Prolog に関するメモ
REPL でも述語を定義する方法
['user']
で REPL でも述語が定義できる。
| ?- ['user']. woman(mia). ^D
部分適用
Prolog で部分適用。
?- ['user']. add(X, Y, X+Y). |: true. ?- add(1, 2, R). R = 1+2. ?- call(add, 1, 2, R). R = 1+2. ?- call(add(1), 1, R). R = 1+1.
この記事で使われてて知った。
演算子
Prolog、演算子を定義したり term_expansion
を駆使すれば任意の言語をほぼそのまま評価できそうな気がする。
と思ったけどカンマ等はどうにもならないっぽい。
Prolog の op/3 の定義の仕方と、以下の記事の演算子の優先順位の定義の仕方が似てる。 Prolog 処理系もこんな感じでパースしてるんだろうか。 二項演算子以外は Prolog で定義できるのか知らないけど(三項演算子とか前置・後置演算子)。
<Insert Language Name Here> - How to make interesting little languages.
と思って調べたらできるっぽい(前置・後置の単項演算子を定義)。
三項演算子は二項演算子の組み合わせだから cond ? expr1 : expr2
では ?
と :
の演算子を定義すればいいはず?
演算子定義のエラー回避
演算子を定義しようとしたけどできない、って時は op(400,xfx,'||').
みたいにシングルクォートで囲ってやれば演算子定義の際のエラーを回避できるかもしれない (よくわかってない)。
または :- multifile (=>)/2.
みたいにすれば演算子をエクスポートする際のエラーを回避できるかもしれない (よくわかってない)。
Prolog 初学者向け解説
以下のページがよくまとまってて読みやすかった。 英語だけど分量が少ないし説明が簡潔。
A Concise Introduction To Prolog
あとこの言葉が好き。
Prolog is a notation for stating logical relations that happens to be executable.
というわけで以下上記のページで気になった項目をつまみ食い。
ジェネレーター
述語は複数の値を返せる。
Many predicates can be used as generators, to generate one solution after another until later conditions are met. For example,
?- member(X, [1, 2, 3, 4, 5]), X > 3. X = 4 ; X = 5.
succeeds and instantiates X to 4. If backed into, it re-instantiates X to 5. (But if you think declaratively, this just says "X is a member of the list [1, 2, 3, 4, 5] that is greater than 3.'')
例えば以下の記事では前のバージョンのコードでは find_tree という述語を作っていた。
% Path = [], Node = a++[b++[a++[]]] になる (複数個の回答は考えられてなかった…はず) find_tree(a++[b++[a++[]]], =(a++_), Path, Node).
でも Prolog ではそのような述語を作る必要が無く、単にツリーを traverse して各ノードを返すジェネレーターを作ればそれで事足りる。 例えば以下の様に(テストコードから引用)。
% a++_ にマッチするノードをリストアップ。ここでは以下のペアが返される。 % Path = [], Node = a++[b++[a++[]]] % Path = [0, 0], Node = a++[] traverse_tree(a++[b++[a++[]]], Path, Node), Node = a++_.
ジェネレーターのそれぞれの返り値をリストにしたければ findall/3
を使う。
1番目の引数には2番目の引数の式でキャプチャ (自分語録) したい変数を式の形で示すとその通りに3番目のリストに入る。
ここでは (Path, Node)
と指定しているので、
Results は [([], a++[b++[a++[]]]), ([0, 0], a++[])]
となる。
Node = a++_, findall((Path, Node), traverse_tree(a++[b++[a++[]]], Path, Node), Results).
Prolog は式をその場で評価しない。
例えば Two = 1+1
の Two
は 2
ではなく 1+1
になる。
ちなみに数値として評価したい場合は is
を使う (Two is 1+1
)。
そして (Path, Node)
はタプルみたいなもので、C 等で (1+2)*3
とする場合の優先順位を示す括弧とは全く違う (という認識)。
2つの変数の式の形を示せればいいので、2要素のリストとする事もできる。
Node = a++_, findall([Path, Node], traverse_tree(a++[b++[a++[]]], Path, Node), Results).
この例も分かりやすかった。
Simple Prolog generator - Stack Overflow
Cut-Fail
あ、これ再帰の深い所から抜け出して値を返したい時便利そう。 再帰の深い所で失敗した場合、再帰でちまちまバケツリレーしてたけど、cut-fail を使えばシンプルに書ける (はず)。
When one clause fails, the next one is tried. If you want the failure of a clause to cause the failure of the entire predicate, you can use a cut-fail combination:
sqrt(X, RootX) :- X < 0, !, fail. (more clauses of sqrt should follow)
This is a procedural shortcut that avoids the necessity of having X <= 0 in every clause; it is justified only if the test is complex and there are many clauses.
動的に述語を定義
動的に述語が定義できるらしい。 ただそれが良い書き方かどうかは分からない。 イミュータブルじゃなくなる気がするので、個人的には避けた方が良いような気もする?
Database manipulation predicates
assert(X)Add X to the database. For syntactic reasons, if X is not a base
ツイ
Prolog、ツリー構造のライブラリ書いてて一番楽しいって思ったの、ノードのパスを指定するとそれをノードと全体の式に分離する述語を思いついた時だった。
— tyru🍆 (@_tyru_) 2018年9月7日
split_tree(+Tree, +Path, -Node, -Expr :: -Node).
で、こんな感じになる
split_tree(a++[b++[]], [0], b++[], a++[Node] :: Node).
これを使えばノードの取得は単に Node を取って他は捨てればいいし、更新は Expr :: Node の Node を更新 (と言っていいのか分からんけど) すれば全体の Expr が求まるし、何より変数を含む式を返してもいいって事に気付けたのが一番の収穫だったと思う。
— tyru🍆 (@_tyru_) 2018年9月7日
それまでは callback 渡してツリーとノード渡して返す値を決めさせようか…とか手続き的に考えてたんだけど、全体とノードを抜き出した「式」を返せば呼出し元でどうにでもできるじゃんっていう気付き。
— tyru🍆 (@_tyru_) 2018年9月7日