Prolog 写経記 その 88 if/1
(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.
- 作者: ボグダンフィリピッチ,中島誠,伊藤哲郎
- 出版社/メーカー: 海文堂出版
- 発売日: 1990/08
- メディア: 単行本
- 購入: 4人 クリック: 33回
- この商品を含むブログ (68件) を見る
順番では
case/1
になるのですが,その中で使われている if/1
から写経します.解説
条件の定義.
if P then Q
はもしP
が成功したならば Q を実行する.if P then Q else R
は,もしP
が成功したならばQ
を実行し,そうでなければR
を実行する.
ふむ.
まるっきり Prolog らしからぬしろものですな.
モード
if(+)
ふむ.
っていうか,then
や else
はいずこへ?
定義
では,こいつの定義を写経しませう.
:- op(900, fx, if). :- op(850, xfx, then). :- op(800, xfx, else). if P then Q :- if_then_else( Q = (R else S), if_then_else(P, R, S), if_then(P, Q)). if_then(P, Q) :- call(P), !, call(Q). if_then(_, _). if_then_else(P, Q, _) :- call(P), !, call(Q). if_then_else(_, _, R) :- call(R).
うーみゅ... そう来たか...
っていうか... これは手強い...
まずはあまり見慣れているとは言えない最初の :- op(...)
について.
これはオペレータ宣言といって,述語というかファンクタを単項あるいは二項演算子のように使うことを宣言するものです.
ファンクタというのは
f(a, b, c).
みたいな複合項の名前.この例では f
.
実は Prolog では +
(単項も二項も) などの演算子はもちろん,普段何気なく使っている :-
なんかもオペレータ宣言された述語に過ぎなかったりするようです.
つ・ま・り
A = B.
という記述,実は
=(A, B).
の簡略記法に過ぎないという次第.
んで,
:- op(900, fx, if). :- op(850, xfx, then). :- op(800, xfx, else).
ここでは三つのファンクタのオペレータ宣言をしています.第三引数が対象のファンクタ.
最初の引数は結合度,いわゆる優先度で,数値が小さい方が優先度が高いものになります.if
が一番優先度が低くて,else
が一番高いということです.
そして第 2 引数がキモ.
まず if
の場合の fx
ですが,これはファンクタ f
が引数 x
の前置単項演算子ということを意味します.xf
だと後置単項演算子.
引数を示すのが x
だと同じ優先度の演算子を結合できないことを意味しているので,
if if P
とは書けないことになります.
ちなみに引数を y
で表して fy
とすると同じ優先度の演算子を並べることができる,右結合の前置単項演算子になります.例としては not/1
.
not not P
と書くと
not (not P)
のように扱われます.これは実際には
not(not(P)).
と書いたのと同じ.
そして xfx
は非結合の二項演算子.==
や <
等の比較演算子みたいな感じ.
ちなみに yfx
だと Java なんかの代入演算子のような右結合の二項演算子,xfy
だと *
や /
のような左結合の二項演算子になります.
ともあれ (JW),
if P then Q.
と書くと
if(then(P, Q)).
のように解釈されるとみた.
if
よりも then
の方が優先度が高いので,まずは
if (P then Q)
と解釈されて P then Q
のところが then(P, Q)
となり,if
はこの項を引数として受け取る,と.たぶん.
同じく
if P then Q else R.
と書くと,
if(then(P, else(Q, R))).
になるとみた.
ちなみに,ここでの then
とか else
とかは関数のように評価されるわけではないことに注意.
最初に else/2
が評価されて,その後 then/2
が評価されて,そのさらに後に if/1
が評価される,というわけではありません.
ここではthen(P, else(Q, R))
という項を引数として if/1
が呼び出されるだけです.
たぶん.
そうか,だからこの述語は単に if/1
で,then
も else
も出てこないのか.
なるほどなるほど.
そんなわけで (どんなわけで?),述語の定義へ.
まずは本体.
if P then Q :- if_then_else( Q = (R else S), if_then_else(P, R, S), if_then(P, Q)).
ぐはぁっ
って感じですが,述語を使う場面のみならず,定義でもオペレータ記法を使ってくれてますね.
これの頭部は
if(then(P, Q)) :-
と書いた場合と同じはずなので,唯一の引数が then
というファンクタでアリティが 2 の複合項である場合にマッチします.たぶん.
節の本体は一見ややこしいですが,実は単純に if_then_else/3
を呼び出しているだけ.その引数がちょっとややこしいですが,具体化された変数は展開されたかのように項が組み立てられることがポイントかと.
例えば
if N == 0 then write('zero') else write('not zero').
なんて場合は,まず
if(then(N == 0, else(write('zero'), write('not zero')))).
となって,このとき if/1
の各変数は
- P
N == 0
- Q
else(write('zero'), write('not zero'))
となります.よって,if_then_else/3
に渡される引数はそれぞれ
else(write('zero'), write('not zero')) = else(R, S)
if_then_else(N == 0, R, S)
if_then(N == 0, else(write('zero'), write('not zero')))
となります.
その if_then_else/3
ですが,2つの節があります.まずは最初の節.
if_then_else(P, Q, _) :- call(P), !, call(Q).
まずは第 1 引数を評価します.上の例では
else(write('zero'), write('not zero')) = else(R, S)
ということになり,このマッチングは成功して R
と S
がそれぞれ
- R
write('zero')
- S
write('not zero')
に具体化されます.
そこで第 2 引数を評価します.上の例では
if_then_else(N == 0, write('zero'), write('not zero'))
なので,ある意味再帰呼び出し.っていうか,間違いなく再帰呼び出し.
そこでは N == 0
が評価されますが,これは元々の条件そのものです.
これが成功すれば第 2 引数が呼び出されますが,それは例では write('zero')
ということで,意図したとおりです.
なんかすげー.
もし N == 0
が成立しなければバックトラックにより次の節
if_then_else(_, _, R) :- call(R).
が試されます.ここでは無条件に第 3 引数,つまり write('not zero')
が評価されます.
かなりすげー.騙されてる気分...
ちょっと戻って,もし else
を付けずに
if N == 0 then write('zero').
だったらどうなるか?
if/1
が if_then_else
を呼び出すときの引数はそれぞれ,
write('zero') = else(R, S)
if_then_else(N == 0, R, S)
if_then(N == 0, write('zero'))
となります.
すると if_then_else/3
の call(P)
は write('zero')
が else(R, S)
とマッチしないため失敗に終わり,バックトラックにより次の節へ移ります.
そこでは第 3 引数
if_then(N == 0, write('zero'))
が評価されます.
そこで if_then/2
.
if_then(P, Q) :- call(P), !, call(Q). if_then(_, _).
最初の節で P
つまり条件を評価して,成功すれば Q
を評価します.
P
を評価して失敗した場合はバックトラックして次の節へ移りますが,何もせずに成功します.
めっさすげー.
そんなわけで (どんなわけで?),確かに if P then Q
も if P then Q else R
もうまく動くようです.
えらく凝ってるというか巧妙ですな.
if_then_else/3
に渡す引数なんて,高階関数の趣がありますね.
正直,今の自分ではこいつの動作を理解するのが精一杯,こんな述語を書くことはとうてい無理です.
論理プログラミングとか宣言的プログラミングとは全然関係ないと思うけれど,これはこれでかなりすごい.
Prolog も Lisp なんかと似ていて,プログラムも単なるデータという感じなのですが,これはそれを有効に使っているような感じ.
オペレータ宣言と相まって,Prolog で DSL を考えるのも楽しいかも〜.
注意
if P then Q
において,もし条件P
が失敗すれば何も実行されず,if
は成功する.
ほいほい.
例
では使用例を写経しませう.
warning(X) :- if X < 0 then (write('Value is negative.'), nl).
これの使用例はないので適当に試すべし.
3 ?- warning(0). Yes 4 ?- warning(1). Yes 5 ?- warning(-1). Value is negative. Yes
ふむ.
述語の定義がすごかった割に,使うとつまんない...