高等魔術の教理と祭儀
とてもタイトルから見るとプログラミング言語の勉強会とは思えませんが、内容は非常に深い Prolog の勉強会でございました。
ATND のアジェンダはこちら。
スライドはこちらにて公開されております。
このエントリは私がついったのハッシュタグ CoP でメモった内容を、上記アジェンダに合わせてコピペしたものを元にしておりますが、なにせ2009.12.26の勉強会でございます故記憶が抜け落ちたり間違ってたりする可能性は何卒御容赦。
そもそもこの会って?
今年のGWにPPIMっていうPrologの勉強会(というかPrologを実装してみようって会)があって、そこで m0h1can さんが「The Craft of Prolog いいよ!」と言ってたら、「じゃあそれを元に勉強会しましょうよ!」みたいなことで始まった……らしいです。
意外や意外、精鋭7人も集まりかなり盛り上がりました。
オイラが一番レベル低かったんじゃないかな?
The Craft of Prolog は良書とはいえ何せ古いので、最新のトピックを m0h1can さんが補うという形で話が進みました。
基本は m0h1can さん作のスライドで進んだのですが、途中でかなりディープな議論などありまして非常に面白かったと思います。
スライドはこちら。
復習しておきたいこと
- ありがちなmemberの実装。?-member([a,b,c],X).するとユニフィケーションでX=aが得られる。失敗させるとバックトラックしてb、cが順に得られる。まあ常識。
member([X|_], X). member([_|Y], Z) :- member(Y, Z). ?-member([a,b,c], X). member([a|b,c], a) X = a ; member([a|b,c], X) :- member([b,c], X). member([b|c], b). X = b ; member([a|b,c], X) :- member([b,c], X). member([b|c], X) :- member([c], X). member[c|[]], c). X = c ; fail.
- Prologは対話的に実行されているように見えるだけで、実際はトップレベルが反復しているだけ。
- 対話的にfailさせる';'はcurrent_op(1100,xfy,";")である。
- Prologにおけるリストはオペレータ'.'で書き下すことができる。やっていることは Lisp の Cons セル。さっきの member を書き直すとこんな感じ。
member('.'(X,_), X). member('.'(_,Y), Z) :- member(Y, Z). ?-member('.'(a,'.'(b,'.'(c,[]))), X). member('.'(a,'.'(b,'.'(c,[]))), a) X = a ; ... 以下略(資料参照)
- 一階述語論理の完全性とバックトラックは不可分。
- Prologのユニフィケーションは例えば Erlang などのパターンマッチと同じ? 違う? Prologのユニフィケーションはバックトラックと不可分? という議論があった。
- 結論としてはユニフィケーションそのものは他の言語のパターンマッチングと同じ(=バックトラックとは不可分でない)ととらえていいということになったんだっけ?
-
- これを Prolog では以下の用に書く。
P :- P1, P2, P3, ... Pn
はじめにインデントありき…コーディングスタイル
% BAD hoge(X) :- (C -> A1, A2 ; B1, B2), P. % GOOD % 真となるリテラルを揃える hoge(X) :- ( C -> A1, A2, ; B1, B2), P.
- accumulator を使う例はこのような感じ*2。
sum(List, Sum) :- sum1(List, 0, Sum). sum1([], Sum, Sum). sum1([H|T], S1, Sum) :- S2 is H + S1, sum1(T, S2, Sum).
sum(List, Sum) :- sum1(List, 0, Sum). while { sum1(....) }
ちょっと脱線
神は二日目に変数や定数や構造体を作ろうとして述語を作った…Prologは一階述語論理です
- Prologは一階述語論理言語。一階述語論理で書きましょう。
- 論理は文字に意味を持たせない。メタキャラクタで記述する。
- 例えばx=a+b→'='('+'(A,B), X)と書くのが本当。
- '+'/2は加算するという演算を行うわけ「ではない」
- これはあくまで'+'(A,B)とXが'='/2で結びついているという意識を持つことがポイント。
- '='/2はユニフィケーションを明示的に行う述語
- 数値演算はis/2で書く。
- Q:'='/2 と is/2 はコンテキストで区別つくから一緒にするという言語拡張はありえないの?
- A:意味が違うものを同じにしてもあまり嬉しくないよーな。
- C++ でいうと Prolog のコードは ExpressionTemplate
俺がお前でお前が俺で…Prologの特徴はユニフィケーションです
- ユニフィケーションになれましょう。
- ?-[1,2,3]=[X|Y].->X=1, Y=[2,3]. まあここら辺は他言語のパターンマッチ(特に Erlang) と同じ。
- 差分リスト。L=[1,2|Hole]は後ろに(未束縛の引数という穴が開いた)リスト。後ろに束縛しちゃえば一瞬でappend/3できるのでO(1)でできる。
ap(X-Y,Y-Ys,X-Ys). ?- ap([1,2|Xs]-Xs,[3,4|Ys]-Ys,Z). => Z=[1,2,3,4|Ys].
- では Z=[1,2,3,4]を返すためにはどうする?
- 正解はこんな感じ。
ap(X-Y,Y-Ys,X-Ys). ?- ap([1,2|Xs]-Xs,[3,4|Ys]-Ys,Z-[]). => Z=[1,2,3,4].
-
- Q: この「穴が開いた構造 (= 変数が未束縛のままで先に進めてしまう性質)」を使ったおもしろい応用って他にないですか?
- A: ちょっと思いつかない。みなさん考えて見てください。
行ったり来たりは切り捨て御免…グリーンカットとレッドカット
- ご存知 repeat - fail ループ(失敗駆動ループともいうかな)。
% 何回バックトラックをしても成功する述語。 % 組み込み述語なので再定義は必要ないよ(下はあくまでも例)。 repeat. repeat :- repeat. ?-repeat,write('かわいい?(y/n)'),read(X),(X=y->!;fail),write(ok).
- !/0カット操作は現在のバックトラック候補を、今のクエリの呼び元のバックトラックに変える。
- カットの種類
% Nagation as Failure の例 \+P :- call(P), !, fail. % max の例 max(X, Y, X) :- X >= Y, !. max(X, Y, Y).
ホリ・ススムがクイックスをやるとどうなるか…PrologはDFSです
- Prologの実行セマンティックスであるSLD導出はDFS(Depth First Search;深さ優先探索)なのでそのまま書けば深さ優先で証明木を書ける。
- BFS(Breath First Search;幅優先探索)の場合は自前でキューを書いてあげなきゃいけないのでちょっと頑張らないとダメ。
answer(X) :- start(S), queue(S, Q), breadth_star(Q, X), solution(X). breadth_star(Q, Y) :- queue_head(X, Q1, Q), ( Y = X ; children(X, Children), queue_last_list(Children, Q1, Q2), breadth_star(Q2, Y)).
- しかも使いたいデータ構造によっていちいちキューを書かないといけない(テンプレート以前のC++みたいだ)。
念心合体、ゴー!アクエリオン!…Prologはやっぱりユニフィケーションです
- copy_term/2 という述語 (ある述語を渡すと、未束縛の変数で引数を置換した述語をコピーして渡してくれる)を使うことでお手軽クロージャが書けるよ! という話。
- 言語の中に手を突っ込んでるのでチートくさいがISO標準なので大抵使えますよ
- 例!*6
app(Lambda, Args) :- copy_term(Lambda, Skelton), Skelton =.. [lambda, Args, Body], call(Body). ?- C = lambda([X], (write(X), write('は一つ歳をとりました'), nl)), app(C, ['雪歩さん']), app(C, ['春香さん']). 雪歩さんは一つ歳をとりました 春香さんは一つ歳をとりました true.
-
- ミソはここで二つの app/2 の呼び出しで同じXを束縛していること
- copy_term/2 がなかったら一個目の束縛で二個目が成立せず fail してしまう
- さらにループを書く例とかあるので資料読め!
後で後悔しないために…先にしておく高階述語
バベルの図書館入館申請書…知識ベースの取り扱い
ATフィールド中和…Prologは知識をマージします
味付けにマクロを少々……Prologは二度死ぬ
*1:証明失敗すると無限ループにハマる、という理解でよいのかな。
*2:あ、Prolog 初心者 (がここ読んでるかは疑問) に言っておくと Prolog は変数の再代入ができない言語なので再帰でループするときには計算用の引数を一個追加するというのが定石なのです。これを accumulator と言います。
*3:でも Ubuntu Karmic だとフォントが全部全角フォント幅になって非常に悲しい。どっかで対策見た気がするんだけど。
*4:なお Prolog の否定マーク \+ は論理記号の T を左90度回した文字を negate のストロークを入れた形を模してるそうな。知らんかった。
*5:Nagation as Failure の意味論については資料参照。
*6:この例について物言いがあって d:id:ranha:20091227:1261922172 「雪歩さんは年を取りません!」だそうですが、そうなの?
*7:学生時代使っていた Prolog にはなかった記憶が……>dynamic宣言。
*8:ここ文章だとなにいってるか分からないと思うけど、資料公開されたらソースはります。ご勘弁。