K-Prolog Compiler Version 6.0 
28: ?- 質問. :- 命令.命令や質問はゴールを","で区切って並べたものでした。 この","は論理的には AND を意味する演算子ですが、 Prologの処理系は、これらのゴールを左端に書かれたものから順に実行しようとします。
[例]
28: ?- capital(japan,C).
は、前節の例に従えば、
C = tokyo
を結果として返してきます。
ゴールを実行すると、そのゴールと同じ名前で同じアリティの述語が呼び出されます。
述語が呼び出されると、その述語の節のうちで頭部がそのゴールと単一化できるものを
探します(単一化については後述します)。
探す順序は上から下に節が書いてある順です。頭部が単一化できる節があれば、 その節の本体部をゴールとして実行します。 本体部は","で区切られたゴールですから、 左端のゴールから順に実行します(本体部がなければ直ちに実行が 成功します)。 本体部のゴールの実行がすべて成功すればその節(あるいはその述語) の実行が成功したことになります(成功しないことを 失敗と言います)。
ここでは、ゴールの実行が普通のプログラム言語と同じように 述語を手続きとして呼び出す形式で行うこと、 述語の実行が節の探索を含むこと、 節の本体部の実行が他の言語と同じく書いてある順に行われることを理解してください。
[例] 29: ?- capital(Nation,peking),population(Nation,Population). Nation=china, Population=118000
capital(Nation,peking) と capital(usa,washington)は、Nationにどんな値を代入しても、peking と washington は、 同一にならないので単一化できません。しかし、
capital(Nation,peking) と capital(china,peking)は、Nation=china という代入で同一になるので単一化できます。
さて、ゴールが実行されるときには、 呼び出された述語の節の頭部と単一化されます。 このとき、節に含まれている変数は、 全く新しいものを一揃い用意するものと考えてください。 名前が同じものがたまたま存在しても別の変数であると 考えてもよいでしょう。変数名の有効範囲は1つの項の中でしたから、 1つの項である節の中の同じ名前の変数は常に同じものです。
このようにして、ゴールと節の頭部の単一化により、引数の受け渡しが行われます。 変数への値の代入は常にこの単一化によって達成されます。
単一化のプロセスを規則として書くと次のようになります。
[例]
祖母(_祖母,_孫):-
母親(_祖母,_親),親(_親,_孫). ・・・(1)
祖父(_祖父,_孫):-
父親(_祖父,_親),親(_親,_孫). ・・・(2)
親(_親,_子):-父親(_親,_子). ・・・(3)
親(_親,_子):-母親(_親,_子). ・・・(4)
母親(フネ,サザエ). ・・・(5)
母親(フネ,カツオ). ・・・(6)
母親(フネ,ワカメ). ・・・(7)
母親(サザエ,イクラ). ・・・(8)
父親(波平,サザエ). ・・・(9)
父親(波平,カツオ). ・・・(10)
父親(波平,ワカメ). ・・・(11)
父親(鱒夫,イクラ). ・・・(12)
祖母/2、祖父/2 と 親/2 はプログラムとして規則を表す述語です。
母親/2 と 父親/2 は、データとして事実を表す述語です。これらの述語が読
み込まれているときに、
29: ?- 祖父(波平,_孫).
つまり、波平が祖父であるような_孫はだれかという命令を実行すると、
_孫 = イクラ
が答として返ってきます。この命令の実行の状況を図示してみます。
29: ?- 祖父(波平,_孫).
_祖父=波平,_孫=_孫
(2) 祖父(波平,_孫):- 父親(波平,_親),親(_親,_孫).
サザエ=_親
(9) 父親(波平,サザエ).
_親=サザエ,_子=_孫
(3) 親(サザエ,_孫):- 父親(サザエ,_孫).
失敗
_親=サザエ,_子=_孫
(4) 親(サザエ,_孫):- 母親(サザエ,_孫).
_孫=イクラ
(8) 母親(サザエ,イクラ).
まず、述語、祖父/2 が呼ばれます。第1引数の 波平 と _祖父 は単一化されます。
_孫 と _孫 も単一化されて同じものになります。
次に本体部の最初のゴール 父親(波平,_親) を実行しますが、 これは(9)の単位節により成功します。 次のゴールに進むときに、単一化の効果によって _親=サザエ になっています。
親(サザエ,_孫) は、(3)の節の頭部と単一化され、 本体部の 父親(サザエ,_孫) を実行しますが、 父親/2 にはこのゴールと単一化できる節がないので、失敗します。 ここで後戻りが発生し、その結果(3)の節の次の節が選ばれて、 (4)の頭部と単一化されます。
(4)の本体部、母親(サザエ,_孫) は(8)の単位節により、成功します。 このとき、_孫=イクラ が単一化の効果として残っていますが、 (4)の _孫 は、トップレベルの_孫と単一化されているので、結果として、
_孫 = イクラ
が表示されるわけです。
入出力は、ファイルのオープン、クローズを行う see、tell や seen、told などと、 実際に読み書きを行う read、write や get、put などの組込み述語で行われます。 述語や節のうち動的なものは、assert や retract、abolish という組込み述語でプログラムの実行中に追加、削除されます。
インタプリタの状態を変える述語は他にたくさんあって、 たとえば、unknown という組込み述語は、定義されていない述語の実行の方法 を変更します。
foo(x): -write(X),checka(X). foo(x): -write(X),checkb(X). checka(a). checkb(b).foo/1 は引数が a または b と単一化できるときに成功する という意味を持ちますが、副作用としては、write/1 によって引数の値が書かれます。
foo(a) foo(a) :- write(a),checka(a). aが書かれる checka(a). 成功 ; foo(a) :- write(a),checkb(a). aが書かれる checkb(a). 失敗 foo(b) foo(b) :- write(b),checka(b). bが書かれる checka(b). 失敗 foo(b) :- write(b),checkb(b). bが書かれる checkb(b). 成功
引数がaのときは、
aが書かれる→成功する→ 後戻りを起こせば→aが書かれる→失敗するの順になります。
bが書かれる→失敗、後戻り発生→ bが書かれる→成功する→ 後戻りが起これば→失敗するの順です。
write/1 の効果は後戻りによって元に戻りませんから、 引数が a と b の場合で動作が異なります。 write(X) のところが副作用のない述語ならば、 引数 a と b については foo/1 は対称ですから同じ動作になるのは明らかです。
foo/1 の定義を書き換えて
foo(X) :- checka(X),write(X). foo(X) :- checkb(X),write(X).とすれば、引数 a,b に対する動作は、副作用も含めて(foo/1 の外からみれば)同じになります。
さらに
foo(X) :- checkc(X),write(X).の形の節がたくさんあると仮定します。この述語は一般的に、 引数の内容に従って処理を場合分けする方法のモデルになっています (たまたま、処理は全部同じです)。
このとき、引数 a に対する動作は a を出力するだけですが、 その後後戻りによって全部の節が調べられ、 checkb/1 や checkc/1 によって失敗します。 checka/1 や checkb/1 が場合分けとして排他的になっていれば、 失敗する節の探索は無駄な動作になります。これを排除するために、 Prolog には !(カット) という組込み述語があります。 !を使って foo/1 を書き直すと、
foo(X) :- checka(X),!,write(X).
foo(X) :- checkb(X),!,write(X).
foo(X) :- checkc(X),!,write(X).
! は、その ! よりも前にその節の中で残された代替節が
選択されないようにする記号です。
foo/1 自身の他に checka/1 にも代替節があれば、選択されないようになります。
! 自身は組込み述語として成功します。
この ! を使うと、Prolog言語にいくつかの重要な記述力が付加されます。
頭部:- 条件1,!,処理.
頭部:- 条件2,!,処理.
・
・
・
の形の述語です。
頭部:- 条件,!,fail.
頭部.
の形では、必ず失敗する組込み述語fail を使って条件の否定を表現していま
す。(条件が成功するときに限り失敗する)
同じことは組込み述語 not(または \+ ) でも表現できますが、これらは、
実は、!,fail を使って実現されています。
repeat,処理,終了条件,!,・・・繰り返しのループを記述するために、 この形の本体部をしばしば利用します。repeat は組込み述語で
repeat.
repeat :- repeat.
と定義されています。これは、また、
repeat.
repeat.
repeat.
・
・
・
と続く無限個の節と同等です。
repeat の目的は、代替節を限りなく提供することによって
後戻りによるループを実現することです。
repeat,read(X),process(X),X==end_of_file,!,....は入力中のファイルから、項を1つずつ読みとって X とし、process/1 に 渡します。process/1 は必ず成功するものと仮定しますが、 その次の X==end_of_file が終了条件です。 == は項が同一であることを調べる組込み述語で、 X が end_of_file でなければ失敗します (end_of_file はファイルの終わりをあらわす項です)。 この失敗によって後戻りが起こりますが、process/1 に 代替節がなければ(組込み述語 read/1 にもないので)、repeat の 代替節が選択されて成功し、再び read(X),process(X) が実行されます。
さらに、!を使用した場合の利害を述べておくと、
write_list([E|L]) :- write(E),nl,write_list(L). write_list([]).と定義できます。リスト[1,2,3] をこの述語に与えると、
write_list([1|[2,3]]) :- write(1),nl,write_list([2,3]). 1を書いて改行する。 write_list([2|[3]]) :- write(2),nl,write_list([3]). 2を書いて改行する。 write_list([3|[]]) :- write(3),nl,write_list([]). 3を書いて改行する。 write_list([]).この順で実行されます。
注意しておくべきことは、再帰の際にも選択された節の変数は まったく新しいものが一揃い用意されることです。 再帰的に呼ばれた側の節の変数EやLは、呼んだ側の変数EやLとは違うものです。
リストに限らず、Prologの唯一のデータ構造である複合項自身が 再帰的な構造を持っていることから、Prologプログラムでは 再帰的な構造をもつデータ構造をひんぱんに利用します。 このような構造を持つデータを扱う場合には、 再帰によって述語を自然な形で定義できます。