Prolog Language
現代のPrologを使う
サーチ…
前書き
現代の多くのPrologシステムは継続的に開発されており、言語の古典的な欠点に対処するための新しい機能が追加されています。残念なことに、多くのPrologの教科書や教育課程でさえ、まだ古いプロローグしか紹介していません。このトピックは、現代のPrologがいくつかの問題を克服し、古くなったPrologに登場しているかもしれない、まだ紹介されているかもしれないひどい構文を克服する方法を説明することを意図しています。
整数演算のためのCLP(FD)
伝統的に、Prolog is
と=:=
演算子を使用して算術演算を実行しました。しかし、現在のいくつかのPrologは、整数演算のよりクリーンな代替手段として、CLP(FD)(有限領域上の制約論理プログラミング)を提供しています。 CLP(FD)は、整数値に適用される制約を格納し、これらをメモリにまとめて結合することに基づいています。
CLP(FD)は、それをサポートするほとんどのPrologの拡張であるため、明示的にロードする必要があります。ロードされると、 #=
構文is
と=:=
両方に代わることができます。たとえば、SWI-Prologでは次のようになります。
?- X is 2+2.
X = 4.
?- use_module(library(clpfd)).
?- X #= 2+2.
X = 4.
is
とis
異なり、 #=
は簡単な方程式を解き、両方向で統一することができます:
?- 4 is 2+X.
ERROR: is/2: Arguments are not sufficiently instantiated
?- 4 #= 2+X.
X = 2.
CLP(FD)は独自のジェネレータ構文を提供します。
?- between(1,100,X).
X = 1;
X = 2;
X = 3...
?- X in 1..100.
X in 1..100.
ジェネレータは実際には実行されないことに注意してください。範囲制約だけが保存され、後の制約を組み込む準備ができています。ジェネレータは、 label
述語を使用して強制的に実行することができます(およびブルートフォース制約)。
?- X in 1..100, label([X]).
X = 1;
X = 2;
X = 3..
CLPを使用すると、ブルートフォースケースのいくつかのインテリジェントな削減が可能になります。たとえば、旧式の整数算術を使用します。
?- trace.
?- between(1,10,X), Y is X+5, Y>10.
...
Exit: (8) 6 is 1+5 ? creep
Call: (8) 6 > 10 ? creep
...
X = 6, Y = 11; ...
与えられた条件から数学的に証明できるものの、これらの値は有用ではないとはいえ、Prologは値1〜5をループします。 CLP(FD)の使用:
?- X in 1..10, Y #= X+5, Y #> 10.
X is 6..10,
X+5 #= Y,
Y is 11..15.
CLP(FD)は直ちに数学を行い、利用可能な範囲を計算します。 label([Y])
を追加すると、Xは有用な値6.10だけをループします。このおもちゃの例では、これはパフォーマンスを向上させません。なぜなら、1〜10のような小さな範囲では、代数処理にはループの時間がかかるからです。より広い範囲の数値が処理されている場合、計算時間が大幅に短縮される可能性があります。
CLP(FD)のサポートは、Prolog間で可変です。 CLP(FD)の認知度の高い最善の開発は、商業的かつ高価なSICStus Prologにあります。 SWI-Prologや他の公開されているPrologは、しばしば実装されています。 Visual Prologには標準ライブラリにCLP(FD)は含まれていませんが、拡張ライブラリは使用できます。
失敗駆動型ループの代わりにForall
いくつかの古典的なPrologの教科書は、 fail
作図を使用してバックトラックが強制的にゴールをジェネレータのすべての値に適用する、混乱してエラーを起こしやすいエラー駆動のループ構文を使用します。たとえば、すべての数値を所定の限度まで印刷するには、次のようにします。
fdl(X) :- between(1,X,Y), print(Y), fail.
fdl(_).
Modern Prologsの大部分は、これに対処するために、より高次の述語を提供する代わりに、この構文を必要としません。
nicer(X) :- forall(between(1,X,Y), print(Y)).
これははるかに読みやすいだけでなく、失敗したゴールが印刷の代わりに使用された場合、その失敗は正しく検出されて渡されますが、失敗駆動ループの目標の失敗は強制的な失敗と混同されますループを駆動します。
Visual Prologには、これらのループのためのカスタム構文シュガーがあり、関数述語と組み合わせられています(下記参照)。
vploop(X) :- foreach Y = std::fromTo(1,X) do
console::write(X)
end foreach.
これは必須のループのように見えますが、それでもPrologの規則に従います。特に、 foreachの各繰り返しは独自のスコープです。
関数形式の述語
伝統的にPrologでは、「関数」(1つの出力とバウンドインプットを持つ)が、通常の述部として記述されていました。
mangle(X,Y) :- Y is (X*5)+2.
これにより、関数スタイルの述語が複数回呼び出された場合、一時変数をデイジーチェーンする必要があるという難点があります。
multimangle(X,Y) :- mangle(X,A), mangle(A,B), mangle(B,Y).
ほとんどのPrologでは、代替関数を含む式を展開するis
代わりに使用する代わりの中置演算子を書くことでこれを回避することができます。
% Define the new infix operator
:- op(900, xfy, <-).
% Define our function in terms of the infix operator - note the cut to avoid
% the choice falling through
R <- mangle(X) :- R is (X*5)+2, !.
% To make the new operator compatible with is..
R <- X :-
compound(X), % If the input is a compound/function
X =.. [OP, X2, X3], % Deconstruct it
R2 <- X2, % Recurse to evaluate the arguments
R3 <- X3,
Expr =.. [OP, R2, R3], % Rebuild a compound with the evaluated arguments
R is Expr, % And send it to is
!.
R <- X :- R is X, !. % If it's not a compound, just use is directly
次のように書くことができます:
multimangle(X,Y) :- X <- mangle(mangle(mangle(Y))).
しかし、現代の一部のPrologはさらに進んで、このタイプの述部のカスタム構文を提供しています。たとえば、Visual Prologでは次のようになります。
mangle(X) = Y :- Y = ((X*5)+2).
multimangle(X,Y) :- Y = mangle(mangle(mangle(X))).
上記の<-
演算子と関数型述語は依然としてリレーションとして動作することに注意してください。選択ポイントを持ち、複数の統一を実行することは合法です。最初の例では、カットを使用してこれを防止します。ビジュアル・プロローグでは、リレーションシップの機能構文を使用するのが通常であり、選択ポイントは通常の方法で作成されます。たとえば、目標X = (std::fromTo(1,10))*10
はバインディングX = 10 、X = 20、X = 30、X = 40など。
フロー/モード宣言
Prologでプログラミングするとき、すべての可能なパラメータの組み合わせに対して統一する述語を作成することは常に可能ではない、または望ましいとは限りません。例えばbetween(X,Y,Z)
を表現するbetween(X,Y,Z)
の述語between(X,Y,Z)
XとYの間の数値である.X、Y、Zがすべて結合されている場合(ZはXとYの間、またはXとYが結合されていてZが空いている(ZはXとYの間のすべての数値で統一されているか、またはY <Xの場合には述語が失敗します)。 XとZが結合されていてYが自由であるような他の場合には、潜在的に無数の統一が存在する。これは実装可能ですが、通常はそうではありません。
フロー宣言またはモード宣言により、バインドされたパラメータのさまざまな組み合わせで呼び出されたときの述語の動作を明示的に記述できます。 between
の場合、宣言は次のようになります。
%! between(+X,+Y,+Z) is semidet.
%! between(+X,+Y,-Z) is nondet.
各行は、述部の潜在的な呼び出しパターンを指定します。各引数には、バインドされている場合を示す+
が付いています。そうでない場合は、 -
部分的にバインドされているタプルやリストなど、より複雑な型に使用できる他の装飾もあります。 afterキーワードは 、その場合の述部の動作を示し、次のいずれかです。
-
det
述語は常に選択の余地ポイントに成功した場合。たとえばadd(+X,+Y,-Z)
与えられた2つの数字XとYを加えると、常にちょうど1つの答えが得られるのでadd(+X,+Y,-Z)
はdet
です。 - 述語が成功するか失敗するかは、選択ポイントがない場合は
semidet
ます。上記のように、ZはXとYの間にあるか、そうではないのでbetween(+X,+Y,+Z)
はsemidet
です。 - 述語が常に成功するならば
multi
が、選択点を持つかもしれない(しかしまたそうでないかもしれない)。たとえば、factor(+X,-Y)
はmulti
あると考えられます。なぜなら、数値は常に少なくとも1つの因子そのものを持っているからです。 - 述語が選択点で成功するか、失敗するかどうかを
nondet
します。例えば、between(+X,+Y,-Z)
であるnondet
Y <Xは、それらと述語の間に数字が存在しない場合はXとYの間の数値にZのいくつかの可能単一化があるかもしれないので、または失敗しました。
フロー/モード宣言は、引数のラベリングと組み合わせて、どのような用語が意味するのかを明らかにすることができます。たとえば、 between(+From:Int, +To:Int, +Mid:Int) is semidet
です。
純粋なPrologでは、フローとモードの宣言はオプションであり、ドキュメンテーションの生成にのみ使用されますが、プログラマーがインスタンス化エラーの原因を特定するのに役立ちます。
Mercuryでは、フローとモードの宣言(および型)は必須であり、コンパイラによって検証されます。使用される構文は上記のとおりです。
Visual Prologでは、フローとモードの宣言と型も必須であり、構文も異なります。上記の宣言は次のように書かれます:
between : (int From, int To, int Mid) determ (i,i,i) nondeterm (i,i,o).
意味は上記と同じですが、違いは次のとおりです。
- フロー/モード宣言は型宣言から分離されています(単一の述語のフロー/モードは型のオーバーロードによって変化しないと仮定されているため)。
-
i
とo
は+
と-
使用され、順序に基づいたパラメータと一致します。 - 使用される用語は異なっています。
det
なるprocedure
、semidet
なりdeterm
、及びnondet
なるnondeterm
(multi
まだmulti
)。