このページではJavaScriptを使用しています。

6.デバッガ

6-1.概要

この章では、AZ-Prologインタプリタに組み込まれているデバッガの使い方について説明します。このデバッガは虫取りだけでなく、インタプリタの動作そのものの理解の助けにもなると思います。
まず、次項の「6-2.デバッガの基本的な使い方(トレースとスパイ)」においてデバッガの基本的な使い方を解説します。

6-2.デバッガの基本的な使い方(トレースとスパイ)

AZ-Prologのトレースは、インタプリタが今現在何をしているのかの情報をすべてターミナルに出力します。表示される情報量は多く、どのような局面で情報を表示するかは設定で変えられるようになっています。情報量を選択することも可能です。
なにはともあれ、早速使ってみましょう。
その前に、

| 理解する(松尾さん,ワビ).             /*松尾さんは“ワビ”がわかる。*/
| 理解する(松尾さん,サビ).             /*松尾さんは“サビ”がわかる。*/
| 理解する(ブリキ屋さん,サビ).         /*ブリキ屋さんは“サビ”がわかる。*/
| 理解する(X,風流):- 理解する(X,ワビ). /*“ワビ”がわかる人は風流を理解する。*/
| 理解する(X,風流):- 理解する(X,サビ). /*“サビ”がわかる人は風流を理解する。*/

というプログラムを何等かの方法で入力してください。このサンプルは他の章でも説明に多用しているプログラムです。
邪魔なコードが混じっていない5-4-1.の例の部分を利用する方法もあります。正しく入力出来たか確かめてみます。

| ?-listing.
理解する(松尾さん,ワビ).
理解する(松尾さん,サビ).
理解する(ブリキ屋さん,サビ).
理解する(X,風流) :-
    理解する(X,ワビ).
理解する(X,風流) :-
    理解する(X,サビ).
yes
| ?-

まず、トレースを使ってみます。

| ?-trace.
yes
debug mode on
||?-

これで、次の質問からトレースがかかります。 厳密には「trace/0」が成功した時点からトレースがかかっています。プロンプトが「| ?-」から「||?-」に変わったのは、インタプリタがデバッグモード( 「6-3.デバッグモード」参照)で動いているということを知らせるためです。(トレースまたはスパイをかけると自動的にデバッグモードになります。 ) また、「LOOP=」に続く整数値はインタプリタがゴールを実行する際に述語を呼出した回数(下のトレースの実行例で、Tryした回数と言えば分かり易いですね。)です( 「6-4-3.ループカウンタ」参照)。

||?-理解する(ブリキ屋さん,風流).
 [1] 0 Try   : 理解する(ブリキ屋さん,風流) ?        (1)
  Match   : 理解する(ブリキ屋さん,風流) :-
                理解する(ブリキ屋さん,ワビ).        (2)
 [2] 1 Try   : 理解する(ブリキ屋さん,ワビ) ?        (3)
 [2] 1 Fail  : 理解する(ブリキ屋さん,ワビ)          (4)
 [1] 0 Pop   : 理解する(ブリキ屋さん,風流)          (5)
 [1] 0 Retry : 理解する(ブリキ屋さん,風流) ?        (6)
  Match   : 理解する(ブリキ屋さん,風流) :-
                理解する(ブリキ屋さん,サビ).        (7)
 << LAST CALL >>                                    (8)
 [1] 0 Try   : 理解する(ブリキ屋さん,サビ) ?        (9)
  Match   : 理解する(ブリキ屋さん,サビ).            (10)
 [1] 0 Succ  : 理解する(ブリキ屋さん,サビ)          (11)
yes
LOOP = 3
||?-

これらは以下のようなことを意味します。

(1)これから「理解する(ブリキ屋さん,風流)」を実行します(Try)。
(2)「理解する(ブリキ屋さん,風流)」と単一化可能な頭部を持つ節がみつかりました。それはこの節です(Match)。
     (質問: 「理解する(ブリキ屋さん,風流)」を「理解する(ブリキ屋さん,ワビ)」に置き換えます。)
(3)これから「理解する(ブリキ屋さん,ワビ)」を実行します(Try)。
(4)「理解する(ブリキ屋さん,ワビ)」と単一化可能な頭部を持つ節はありません(Fail)。
(5)「理解する(ブリキ屋さん,風流)」を実行しようとして、途中までうまくいきましたがだめでした。内部状態を元に戻します(Pop)。
(6)「理解する(ブリキ屋さん,風流)」 の別解を探します(Retry)。
(7)「理解する(ブリキ屋さん,風流)」に別の選択肢がありました。(Match)。
     (質問: 「理解する(ブリキ屋さん,風流)」を「理解する(ブリキ屋さん,サビ)」に置き換えます。)
(8)この節は定義の最終節です( << LAST CALL >>)。
(9)「理解する(ブリキ屋さん,サビ)」を実行します(Try)。
(10)「理解する(ブリキ屋さん,サビ)」と単一化可能な頭部を持つ節がみつかりました。それはこの節です(Match)。
(11)「理解する(ブリキ屋さん,サビ)」の実行に成功しました(Succ)

実行例の様に、デバッガの出力するメッセージには「Try」「Match」「Succ」「Fail」「Pop」「Retry」という、実行動作の局面を表す文字列が含まれています。
それぞれがどのような場合に出力されるのか、以上の説明によっておおよそ理解してもらえたと思います。
(詳しくは「6-4.デバッガから出力される情報」を参照してください。)

トレースを終えるには次のようにします。

||?-notrace.
 [1] 0 Try   : notrace ?
 << BUILTIN CALL >>
yes
LOOP = 1
||?-

「《BUILTIN CALL》」というのは「Try」した「notrace」が組込述語であった事を表しています。
トレース終了後のプロンプトを見て気付かれたと思いますが、デバッグモードは解除されていません。これを解除するには次のようにします。

||?-nodebug.
yes
debug mode off
| ?-

トレースは上の例でも分るように、ゴール(質問)の実行の最初から最後までに呼び出された全述語の実行の過程・結果を表示します。
従って、上の例ように簡単なプログラムの場合はともかく、ちょっと大きなプログラムになると出力される情報は膨大な量になってしまいます。
その例として次のサンプルプログラムを試してみましょう。このファイルはAZ-Prologのインストールディレクトリ下の「sampleother」に入っています(Linux版ではshare/azprolog/sample/other)。

| ?-[-'queen.pl'].

これはよく知られた8クイーンを解くプログラムです。
ただし、このプログラムは8クイーンだけではなくnクイーン(即ち、互いに取り合えないようなn×nの盤上のn個のクイーンの配置を求める問題です。)を解くことができます。

| ?-queen(4,X).
X       = [2,4,1,3]
yes
| ?-

試しに、これにトレースをかけてみてください。

| ?-trace.
yes
debug mode on
||?-queen(4,X).
 [1] 0 Try   : queen(4,X_5) ? 
  Match   : queen(4,X_5) :-
                generate(4,L1_11),
                put(L1_11,[],X_5).
 [2] 1 Try   : generate(4,L1_11) ? 
  Match   : generate(4,[4|L_15]) :-
                N1_17 is 4-1,
                generate(N1_17,L_15).
 [3] 2 Try   : N1_17 is 4-1 ? 
 << BUILTIN CALL >>
 [3] 2 Succ  : 3 is 4-1
 << LAST CALL >>
 [2] 1 Try   : generate(3,L_15) ? 
  Match   : generate(3,[3|L_26]) :-
                N1_28 is 3-1,
                generate(N1_28,L_26).
 [3] 2 Try   : N1_28 is 3-1 ? 
 << BUILTIN CALL >>
 [3] 2 Succ  : 2 is 3-1
 << LAST CALL >>
 [2] 1 Try   : generate(2,L_26) ? a    <== Aコマンドを入力し(Enterキー)を押す
 LOOP = 6
||?-

おそらく、うんざりしてきたと思います。
(デバッガが「……?」ときいてきたときに「a 」を入力すればインタプリタのトップレベルに戻ります。)
こんなときのためにスパイがあります。 (下図の実行例で、?の後に縦棒のように見えているのは小文字のL(エル)です。Lコマンドについては「6-5」を参照してください。)

||?-notrace.
 [1] 0 Try   : notrace ? 
 << BUILTIN CALL >>
yes
LOOP = 1
||?-spy put/3.
yes
LOOP = 1
||?-queen(4,X).      
 [1] 0 Try   : put([4,3,2,1],[],X_5) ? l  <== Lコマンドを入力して改行
 [3] 1 Try   : put([3,2,1],[4],X_5) ? l
 [5] 2 Try   : put([3,1],[2,4],X_5) ? l
 [5] 2 Pop   : put([3,1],[2,4],X_5)
 [5] 2 Retry : put([3,1],[2,4],X_5) ? l
 [5] 2 Fail  : put([3,1],[2,4],X_5)
 [5] 2 Try   : put([3,2],[1,4],X_5) ? l
 [7] 3 Try   : put([2],[3,1,4],X_5) ? l
 [7] 3 Pop   : put([2],[3,1,4],X_5)
 [7] 3 Retry : put([2],[3,1,4],X_5) ? l
 [7] 3 Fail  : put([2],[3,1,4],X_5)
 [5] 2 Pop   : put([3,2],[1,4],X_5)
 [5] 2 Retry : put([3,2],[1,4],X_5) ? l
 [5] 2 Fail  : put([3,2],[1,4],X_5)
 [3] 1 Pop   : put([3,2,1],[4],X_5)
 [3] 1 Retry : put([3,2,1],[4],X_5) ? l
 [3] 1 Fail  : put([3,2,1],[4],X_5)
 [3] 1 Try   : put([4,2,1],[3],X_5) ? l
 [5] 2 Try   : put([4,2],[1,3],X_5) ? l
 [7] 3 Try   : put([2],[4,1,3],X_5) ? l
 [9] 4 Try   : put([],[2,4,1,3],X_5) ? l
 [9] 4 Pop   : put([],[2,4,1,3],X_5)
 [9] 4 Retry : put([],[2,4,1,3],X_5) ? l
 [9] 4 Succ  : put([],[2,4,1,3],[2,4,1,3])
 [7] 3 Succ  : put([2],[4,1,3],[2,4,1,3])
 [5] 2 Succ  : put([4,2],[1,3],[2,4,1,3])
 [3] 1 Succ  : put([4,2,1],[3],[2,4,1,3])
 [1] 0 Succ  : put([4,3,2,1],[],[2,4,1,3])
X       = [2,4,1,3]
yes
LOOP = 124
||?-

スパイはトレースすると、大量に出力される情報を人間が指定したものだけに制限します。上の例は、述語queen/2の中で呼ばれるput/3だけに限定して、その実行の流れを辿ったものです。(スパイは、機械語プログラムのデバッグ時によく使用されるブレークポイントに相当します。)
また当然ですが、複数個の述語にスパイを同時にかけることもできます。
スパイがどの述語にかかっているかは次のようにして知ることができます。

||?-debugging.

 << DEBUGGING >>

 Debug Mode    = debug                             (1)
 Unknown       = fail                             (2)
 Spy Points    = put/3                             (3)
 Command Point = Try Retry                         (4)
 Display Point = Try Match Succ Fail Pop Retry     (5)
 Verbose Mode  = middle                            (6)
yes
LOOP = 1
|| ?-

このように「debugging/0」によって

(1)現在デバッグモードか否か、もしくはトレースがかかっているかどうか。
(2)定義されていない述語を実行しようとしたとき、失敗するかトレースを始めるか。
(3)スパイがかかっている述語。
(4)トレース時にどの局面でコマンド入力を受け付けるか。(leash/1で設定。設定方法は「6-5-1.」を参照してください。)
(5)トレース時及びスパイにおいてどの局面を表示するか。(leash1/1で設定。設定方法は「6-4-3.(1)」を参照してください。)
(6)トレースの際に出力する情報量。( leash2/1 で設定。設定方法は「6-4-3.(2)」を参照してください。


という6つの情報を得ることができます。

スパイの外しかたには2通りあります。ひとつは以下のような方法です。

||?-nospy put/3.
yes
LOOP = 1
||?-

もうひとつは「nodebug/0」によってデバッグモードを解除する方法です。
この述語は、デバッグモードを解除すると同時にすべてのスパイを外します。

||?-nodebug.
yes
debug mode off
| ?-

以上は「述語名とアリティ」を指定したスパイポイントの設定・解除ですが、この他に「述語名のみ」「述語名/アリティ/節順位」「述語名/アリティ/節順位/ボディ部のゴール順位」を指定したスパイポイントの設定・解除もできます。

 <例  「述語名/アリティ/節順位」を指定したスパイポイントの設定>

| ?-['trans.dcg'].                                     <== trans.dcg をコンサルト
yes
| ?-listing(jn).
jn([n,'I'],[私|_0],_0).
jn([n,you],[あなた|_0],_0).
jn([n,arrow],[矢|_0],_0).
jn([n,flies],[蝿|_0],_0).
jn([n,flies],[てんぷら|_0],_0).                                <== 5節目。これがTryされるところでトレース開始したい
jn([n,time],[時|_0],_0).
jn([n,like],[好み|_0],_0).
yes
| ?-spy jn/3/5.                                       <== 5節目にSpyをかける
yes
debug mode on
||?-debugging.                                              <== デバッグ情報を確認

<< DEBUGGING >>

Debug Mode    = debug
Unknown       = fail
Spy Points    = jn/3/5                                   <== jn/3の5節目にSpyが設定されている
Command Point = Try Retry
Display Point = Try Match Succ Fail Pop Retry
Verbos Mode  = middle
yes
LOOP = 1
||?-trans("time flies like an arrow",X,Y).
X      = [s,[np,[n,time],[np,[n,flies]]],[vp,[vt,like],[np,[det,a],[n,arrow]]]]
,
Y      = [時,の,蝿,は,ひとつの,矢,を,好む];               <==  ここまでトレースがかからなかったので";"で別解要求
[12] 3 Retry : jn([n,flies],_4_387,[は|_4_377]) ?    
Match  : jn([n,flies],[てんぷら,は|_4_377],[は|_4_377]).       <==  5節目のRetryでトレースが開始されている。
[12] 3 Succ  : jn([n,flies],[てんぷら,は|_4_377],[は|_4_377])
[10] 2 Succ  : jnp([np,[n,time],[np,[n,flies]]],[時,の,てんぷら,は|_4_377],[は|
_4_377])
[13] 2 Try  : jvp([vp,[vt,like],[np,[det,a],[n,arrow]]],_4_377,[]) ? 
[13] 2 Try  : jvp([vp,[vt,like],[np,[det,a],[n,arrow]]],_4_377,[]) ? 
X      = [s,[np,[n,time],[np,[n,flies]]],[vp,[vt,like],[np,[det,a],[n,arrow]]]]
,
Y      = [時,の,てんぷら,は,ひとつの,矢,を,好む]
yes

<例 「述語名/アリティ/節順位/ボディ部のゴール順位」を指定したスパイポイントの設定>

| ?-listing.
a.
a(1) :-    true, write(a1), nl.
a(2) :-    true, write(a2), nl.       <== a/1第2節の第2ゴール、write(a2)がTryされるところでトレースを開始したい
a(3) :-    true, write(a3), nl.
yes
| ?-spy a/1/2/2.
yes
debug mode on
||?-debugging.

 << DEBUGGING >>

 Debug Mode    = debug
 Unknown       = fail
 Spy Points    = a/1/2/2  
 Command Point = Try Retry 
 Display Point = Try Match Succ Fail Pop Retry 
 Verbose Mode  = middle
yes
LOOP = 2
||?-a(X),fail.
a1
 [2] 1 Try   : write(a2) ? p         <== Pコマンドで呼び出し祖先を確認  
         1:  write(a2)
         0:  a(2) :-
                true,
             *> write(a2),          <== トレース開始点が a/1/2/2であることが確認できる
                nl.
         Goal:  ?- a(2),fail.

6-3.デバッグモード

このインタプリタにはデバッグモードがあります。(デバッグモードに対して普通の状態のことをノーマルモードと呼びます。)
前項のようにトレースかスパイをかけると自動的にデバッグモードに入りますが、「notrace/0」や「nospy /fy」を実行してトレースまたはスパイを終了してもデバッグモードから抜けません。
つまり、トレースもスパイもかかっていないデバッグモードの状態がある訳です。
この状態では、ループカウンタを表示する以外、見た目はほとんどノーマルモードと変りません。
ただし、トレースに移行した場合のデバッグ情報を保持するため、LastCallOptimizationを抑止しますのでスタックの消費が増えます。
また、スパイポイントを調べたり、CallCountの蓄積などをおこなうため、実行速度は大幅に(約80%程度)落ちます。
従って、デバッグ時はスタックを多めにとり、デバッグが終わって実際にプログラムを走らせる時には、必ずノーマルモードまたはコンパイルして動かすようにしてください。

デバッグモードか否かを知るには2つの方法があります。

(1)インタプリタのコマンドレベルのプロンプトを見る。

・ノーマルモードの場合
「| ?-」(ユーザモード)あるいは「! ?-」(システムモード)


・デバッグモードの場合
「||?-」(ユーザモード)あるいは「!!?-」(システムモード)


(2)debugging/0を実行してデバッグモードか否かをコンソールに表示する。
ノーマルモードからデバッグモードにするには「debug/0spy /fytrace/0」のうちどれかを実行します。
逆に、デバッグモードからノーマルモードに戻すには「nodebug/0」を実行します。
プログラムの実行中においては、モードの移行はおこなわれませんので、通常はトップレベルにおいて質問、コマンドで与えます。ゴールの中に書かれたこれらのコマンドのうちモードの移行に関するものは、プログラムが終了しトップレベルに戻ってから移行がおこなわれます。ゴールとして「trace/0」を含むプログラムは初めからデバッグモードで実行させてください。
また、デバッグモードのまま「halt/0」「halt/1」でインタプリタを終了しても特に問題ありません。

6-4.デバッガから出力される情報

このデバッガでは、インタプリ夕の基本的な動作の局面を「Try、 Match、Succ、Fail、Pop、Retry」の6つに分けています。ですので、まず「6-4-1.インタプリタの基本的な動作局面」で各局面について説明します。その上で、「6-4-2.局面ごとの出力情報」では、どの局面で何が出力され、それはどのような意味を表しているのかを説明します。「6-4-3.出力情報の制御」では、デバッガが情報を出力する局面を変更したり、情報量を変える方法を説明します。デバッガの出力中に含まれる「LOOP =」について理解して頂くため、ループカウンタについて「6-4-4.ループカウンタ」で解説しています。また、「6-4-5.述語コールカウンタ」では、プログラム実行中にどの述語が何回呼ばれたかの集計情報を表示し、バグの検出や実行効率の向上に役立てる機能について説明しています。

6-4-1.インタプリタの基本的な動作局面
前述した6つの局面のそれぞれがどういう局面なのか、その局面では何が表示され(ここでは簡単に)、この局面が終わってからどのような局面に制御が移っていくのかということについて説明します。

(1)Try
【局面】 インタプリ夕が次に実行すべきゴールに移る時。
【表示】 これから実行するゴールを表示します。
【遷移】 そのゴールが組込述語なら、対応する機械語コードを呼び出し、成功すれば「Succ」に、失敗すれば「Fail」に制御が移ります。定義述語なら、そのゴールと単一化可能な頭部を持つ節を探し、あれば「Match」に、なければ「Fail」に制御が移ります。
(2)Match
【局面】 「Try」で表示されたゴールと単一化可能であるような頭部を持つ節をみつけた時。
【表示】 単一化された節を表示します。
【遷移】 その節が単位節(ゴール列を持たない節)なら「Succ」へ。それ以外は、その節の本体の最初のゴールを「Try」しに行きます。
(3)Succ
【局面】 実行したゴールが成功した時。
【表示】 成功したゴールを表示します。
【遷移】 そのゴールが節の本体の最後のゴールなら、節の頭部が成功し「Succ」へ、続くゴールがあれば、そのゴールを「Try」しに行きます。
(4)Fail
【局面】 組込述語の実行に失敗した時。あるいは「Try」または「Retry」するゴールと単一化可能な頭部を持つ節がなかった時。
【表示】 失敗したゴールを表示します。
【遷移】 「Pop」へと移ります。
(5)Pop
【局面】 バックトラック(後戻り)している時。
【表示】 バック卜ラックして不用になったゴールを表示します。
【遷移】 別の選択肢(その頭部が「Pop」したゴールと単一化可能かどうかまだ調べていない節)が見つかるまで「Pop」が続き、見つかった時点で制御は「Retry」へ移ります。
(6)Retry
【局面】 別の選択肢(代替節)が見つかった時。
【表示】 代替節を実行するゴールを表示します(「Try」の表示と同じ)。
【遷移】 未だ調べていない節から探し始める以外は「Try」同じです。


6-4-2.局面による出力情報

トレースをかけると、インタプリタが6つの局面の内のどれかに達した時、その局面名と現在インタプリタが注目しているゴールまたは節を表示します。これによって、今現在インタプリタが何をしているかがわかります。
ただし、このとき表示されるゴールまたは節は、それらの定義イメージそのままではありません。各局面に共通することなので、局面ごとの出力の説明に移る前に、このことについて若干説明します。


デバッガがゴールや節の情報を表示する場合、その中に現れる変数に値が代入(ユニファイ)されていたら、変数の部分は変数名ではなくその値が表示されます。

<例>

||?-listing.
a(X) :-
    write(X).                   /*節の定義イメージ*/
yes
LOOP = 1
||?-trace,a(b(c)).              /*a(b(c))をトレース実行*/
 [1] 0 Succ  : trace
 [1] 0 Try   : a(b(c)) ?
  Match   : a(b(c)) :-
                write(b(c)).    /*節中の変数Xはb(c)に置換されている*/
 << LAST CALL >>
 [1] 0 Try   : write(b(c)) ?  
 << BUILTIN CALL >>
b(c) [1] 0 Succ  : write(b(c))
 [1] 0 Succ  : trace,a(b(c))
yes
LOOP = 4
||?-

このように、述語a/1の定義中の変数Xは、トレース実行時には項b(c)にユニファイされるので、各局面で表示される節やゴールでは項b(c)に置き換えられた形で表示されています。
一方、上の例には含まれませんが、未代入の変数の場合は、元々の変数名の後に「_NN」(アンダスコア+領域の取られたセル番号)が付加された形で表示されます。同じ節中でこの形の表示が同じものは同じ変数を表すと考えてください。他の節に同名の変数があっても、セル番号が違ってきますから、別物であることが容易に分かります。

デバッガーから出力される情報は、Matchとそれ以外の局面で内容が異なります。以下にそれぞれの場合を説明しますが、ここに書かれているのは「標準的なdebug情報」についての内容です。それ以外の場合については、「6-4-3.(2)出力される情報量の選択」を参照してください。

(1)Match以外の局面の場合

即ち、Try、Succ、Fail、Pop、Retryの場合の出力情報は以下の通りです。


[シリアルナンバー] スタックの深さ 局面名 : ゴール


ここで、シリアルナンバーは「インタプリテーションが始まってからこのゴールを実行するまでの実際のゴールの積層数」です。
この値と次の「先祖系列スタックの深さ」の差が大きいほど、このゴールの手前に非決定性のゴールが多く含まれていることになります。
スタックの深さは「このゴールからトップゴールまでの先祖系列スタックの深さ」を表しています。(6-5-2.コマンド解説Pコマンド参照)

[15] 1 Try:queen1(4,[4,3,2,1],[],Ⅹ,[],[])?

局面名は「Try」です。
シリアルナンバーが「15」で、スタックの深さは「1」です。
「?」を出力してコマンド(「6-5.デバッガのコマンド」参照)を要求しています。


(2)Matchの場合

Matchの場合の出力情報は以下の通りです。
Match:頭部がゴールと単一化された節
<例>

Match   : put([4,3,2,1],[],L_14) :-
select([4,3,2,1],S_22,D_24),
safe([],S_22,S_22),
put(D_24,[S_22],L_14).
6-4-3.出力情報の制御

ここではデバッガが情報を出力する局面を変更したり、情報量を変える方法を説明します。


(1)情報を出力する局面の選択

通常はすべての局面で情報が出力されますが、 「Try、 Match、Succ、Fail、Pop、Retry」のうち特定の局面に達したときのみ出力するようにleash1/1」によって設定することもできます。
「leash1(整数)」を実行すると、整数を2進数で表したときのビット0~5 (ビット0が最下位ビット:least significant bit)の6ビットの対応するビットが1である局面についてのみ出力されるようになります。ビットと局面の対応は以下の通りです。
Version 8 からは、r,p,f,s,m,t を含むアトムによる設定を追加しました。
r,p,f,s,m,t が、Retry  Pop  Fail  Succ  Match  tryに対応。順序は問わず、それ以外の文字は無視します。

?- leash1(rfpt).  <= retry,fail,pop,try の意味 2'111001 と同義です。


ビット 5 4 3 2 1 0
局面 Retry Pop Fail Succ Match Try


例えば、「leash1(4)」を実行すると「Succ」のときにしか出力しません。
また、「leash1(63)」を実行するとすべての局面について出力されます。

【注意】
組込述語については、その種類(「5-10-1.組込述語」参照)にかかわらずヒープ領域に節の実体が無い為、その実行過程は表示されません。この場合「《BUILTIN CALL》」と表示されます。
また、 ヒープ領域に定義された述語(定義述語)であっても、決定性述語(代替節のない述語)の場合は、6つの局面のうち「Retry」と「Pop」の情報は出力されません。


(2)出力される情報量の選択 

トレースの際に出力する情報量は組込述語 leash2/1 で選択することができます。引数には0~3の整数値を与えます。数値の意味は以下の通りです。何もしなければ、デフォルトは1(標準的な情報出力)に設定されています。「6-4-2.局面ごとの出力情報」で解説した内容がこれに当たります。


0:最小のdebug情報
  Match以外の局面でのゴール表示は「述語名/アリティ」のみ。
Matchの局面での節は何も出力されません。
1:標準的なdebug情報
  「6-4-2.局面ごとの出力情報」を参照してください。
2:最大のdebug情報
 

標準的なdebug情報に加え、局面表示の直後に以下のような情報が出力されます。

「Try」「Retry」「Fail」「Pop」「Succ」 では該当ゴールの位置(親述語名/親述語アリティ/親述語の節順位/節中のゴール順位)
「Match」ではMatchした述語定義の位置(述語名/アリティ/マッチした節の順位)

【記述例】

| ?- listing.
| a:- b,c.
| a:- c.
| c.
| ?-leash2(2),trace.
yes
debug mode on
||?-a.
 [1] 0 Try   :  TopGoal  :a ?     <== トップレベル(コンソール)から入力されたa/0をTryします
  Match   :  a/0/1  :       <== a/0 の1番目の節にマッチした
        a :-
                b,
                c.
 [2] 1 Try   :  a/0/1/1  :b ?   <== a/0 の1番目の節の1番目のゴール(b)をTryします
 [2] 1 Fail  :  a/0/1/1  :b    <== a/0 の1番目の節の1番目のゴール(b)が失敗しました
 [1] 0 Pop   :  TopGoal  :a    <== トップレベル(コンソール)から入力されたa/0をバックトラックします
 [1] 0 Retry :  TopGoal  :a ?   <== トップレベル(コンソール)から入力されたa/0をRetryします
  Match   :  a/0/2  :       <== a/0 の2番目の節にマッチした
        a :-
                c.
<< LAST CALL >>
 [1] 0 Try   :  a/0/2/1  :c ?   <== a/0 の2番目の節の1番目のゴール(c)をTryします
  Match   :  c/0/1         <== c/0 の1番目の節にマッチした
                c.
 [1] 0 Succ  :  a/0/2/1  :c    <== a/0 の2番目の節の1番目のゴール(c)が成功しました 
yes
3:最大に追加されたdebug情報
  最大のdebug情報(2)に加え、通常は出力されない次のトレースが可能になります。
・freezeされたゴールのトレース
・AZ-Prologコンパイラ(azpc)のオプション /debug でコンパイルされたコード(Cコード、バイトコード)のトレース


いずれかを選択するには以下のようにします。
<例:最小のdebug情報の場合>

?- leash2(0).

6-4-4.ループカウンタ

デバッグモードでは、ゴールから述語を呼出した回数をカウントする「ループカウンタ」が動作します。Prologのコマンドレベルから発せられる質問の実行が終了すると、そのカウントの結果が次の様に表示されます。

<例>queen.plが読み込まれているとします。

||?-q(8).

:
:
No.92
 . . . Q . . . .
 . Q . . . . . .
 . . . . . . Q .
 . . Q . . . . .
 . . . . . Q . .
 . . . . . . . Q
 . . . . Q . . .
 Q . . . . . . .

Total = 92
yes
LOOP = 116718

この機能を使って例えば、「同じ動作をする二つのプログラムのどちらが動作効率がいいか(どちらのプログラムがコールの回数が少なくてすむか)」というようなプログラム効率の検証ができます。

細かい話ですが、もう一つループカウンタを使っていて少し「あれ?」と思われる事があるかも知れません。
それでは、次の質問を実行してみましょう。

||?-true.
yes
LOOP = 1
||?-

これは true/0 という節(組込述語)を1回コールしたのですから納得できます。
問題は次の場合です。

||?-true,true.
yes
LOOP = 3
||?-

true/0」を2回コールしたのですから「LOOP=2」となりそうですね。にも関わらず「LOOP=3」と表示されています。
これは、ゴールの区切りの「,」が文法上引数2個のオペレータとして扱われているため、インタプリタはこれを「','(true, true)」と解釈して、先ずこの「 ','/2 」をコール(実行)してからその引数のコール(実行)にかかる為です。

しかし、実際には上で紹介した様な「どちらが動作効率がいいか」というような相対的に比べる使い方をする場合は関係ない事ではあります。

6-4-5.述語コールカウンタ

述語コールカウンタを利用すると、プログラムを実行した際にどの述語(組込、ユーザ定義とも)が何回呼ばれているかをチェックすることができます。


<用途>

  • 呼ばれるはずなのに表示されない、呼ばれるはずのない述語が表示される、などバグの検出
  • どの述語の負荷が高いか(すなわちCall回数が多いか)など、アルゴリズムの検討
  • 論理的に等価なプログラムでもゴールの並び順によって大きく効率が違うことがあるので呼び出し述語量の検討

など


<使い方>

デバッグモードにおいて、次のような形で質問をします。実行するプログラムをゴールで与えます。通常モードではゴールは実行されますが、述語呼び出し回数の集計情報は表示されません。

||  ?- call_count_check(ゴール).

プログラム(ゴール)が終了してトップレベルに戻った時に、使われた各述語がそれぞれ何回呼び出されたかを表示します。ユーザ述語/システム述語/組込述語(これらについては「5-1.述語の種類」を参照)の順に集計・表示されます。

<使用例>

$ prolog_c -c queen.pl
AZ-Prolog Version 8.xx (Linux/x64) 
Copyright (C) SOFNEC CO., LTD. 1987-2015
| ?-debug.
yes
debug mode on
||?-call_count_check(q(4)).
No.1
 . Q . .
 . . . Q
 Q . . .
 . . Q .
 
No.2
 . . Q .
 Q . . .
 . . . Q
 . Q . .
 
Total = 2
===CALL COUNT===
exec_status=succ
==== user ====
safe/3  =>  52
select/3  =>  49
disp2/1  =>  20
disp1/2  =>  20
put/3  =>  17
disp/2  =>  10
generate/2  =>  5
queen/2   =>  1
=== system ===
=== builtin ===
is/2  =>  106
¥== /2  =>  64
write/1  =>  38
e_register/3  =>  4
nl/0  =>  2
yes
LOOP = 404
|| ?-


6-5.デバッガのコマンド

デバッガがある局面に達してその局面に関する情報をコンソールに出力した後に「?」を表示した場合には、デバッガに対してコマンドを入力することができます。(逆に、このような場合にしかコマンドを入力することはできません。)

どの局面でコマンド入力を受け付けるかは、設定によって変更することができます。まず、「6-5-1.コマンド入力局面の選択」でこの設定方法を説明し、次に「6-5-2.コマンド解説」で、使用できるデバッガのコマンドについて解説していきます。


6-5-1.コマンド入力局面の選択

デフォルトでは、「Try」「Retry」の2つの局面にインタプリタが達したときのみコマンド入力を受け付けるようになっています。
どの局面に達したときにトレース情報を表示するかを「leash1/1」によって変えられるのと同じように(前項参照)、どの局面のときコマンド入力を受け付けるかも「leash/1」によって変更する事が出来ます。
局面とビットとの対応は、前項の「leash1/1」と同じです。
Version8からは、r,p,f,s,m,t を含むアトムによる設定を追加しました。
r,p,f,s,m,t が Retry  Pop  Fail  Succ  Match  tryに対応。順序は問わず、それ以外の文字は無視します。

?- leash(rfpt).  <= retry,fail,pop,try の意味 2'111001 と同義です。


ビット 5 4 3 2 1 0
局面 Retry Pop Fail Succ Match Try

また、やはり対応するビットが「1」である局面のときのみコマンド入力を受け付けます。

6-5-2.コマンド解説 

デバッガのコマンドには以下のようなものがあります。
(インタプリタがどの局面にさしかかっているかによって使えるコマンドも変わってきます。すべての局面では使えないコマンドについては、コマンド解説の最初に「使用可能局面:」でこれを示します。)


creep
インタプリタが次の局面に達し次第、その局面に関する情報をコンソールに出力します。連続してこのコマンドを使うと、インタプリタの動作に関するすべての情報がコンソールに出力されます。
;(セミコロン)
retry
【使用可能局面】「Match」「Succ」
「Match」において使われた場合には、単一化可能な頭部を持つ別の節を探しにいきます。
「Succ」使われた場合には、表示されたゴールの別の可能性を試します。
どちらの場合にも、その直後の実行に失敗してバックトラックした場合とまったく同じように動きます。そのような状態を強制的に作りだしているわけです。
A
abort
アボー卜します。
プログラムの実行を強制的に打ち切り、インタプリタのトップレベルに戻ります。
ただし、「errorset/2」または「consult/1」「reconsult/1」の中でこのコマンドが使われた場合には、そこでトラップされます。
abort/0」を実行した場合とまったく同じです。
B
break
プログラムの実行を中断し、新しいブレークレベルに入ります。 Aコマンドとは異なり、「unbreak/0」によって実行を再開することができます。デバッグの途中で、「leash/1」「leash1/1」などを使ってデバッガの出力情報の量をコントロールしたり、プログラムをエディットしたりする時に使います。
break/0」を実行した時とまったく同じように動作します。
D
dump stack
スタックの最底部から最上部までのゴールを全て木構造で表示します。
”[シリアルナンバー] スタックの深さ: ゴール”の形式で表示され、”>*”が現在のスタック位置を表します。
[シリアルナンバー] はトレース時の「インタプリテーションが始まってからの実際のゴールの積層数」と同じです。
”スタックの深さ:”は逆順ですが、Pコマンドと同じ現在ゴールの直系列で「現在ゴールからトップゴールまでの先祖系列の深さ」です。
”スタックの深さ:”がついていない行はオルタナティブの残っている兄弟ゴールおよびその子孫にあたります。
[シリアルナンバー] はバックトラックパス(番号の小さい方向に向かって順番にバックトラックする)でもあります。
6-5-3. でこのコマンドを使ったスタックの状態を調べる例を挙げています。
<例>
[14] 1 Try   : e_register(0,N1_10,N1_10+1) ? d     <== Dコマンド
     [1]   0:  q(4)
     [2]       ┣ queen(4,[2,4,1,3])
     [3]       ┃ ┗ put([4,3,2,1],[],[2,4,1,3])
     [4]       ┃   ┣ select([4,3,2,1],3,[4,2,1])
     [5]       ┃   ┃ ┗ select([3,2,1],3,[2,1])
     [6]       ┃   ┗ put([4,2,1],[3],[2,4,1,3])
     [7]       ┃     ┣ select([4,2,1],1,[4,2])
     [8]       ┃     ┃ ┗ select([2,1],1,[2])
     [9]       ┃     ┃   ┗ select([1],1,[])
    [10]       ┃     ┗ put([4,2],[1,3],[2,4,1,3])
    [11]       ┃       ┣ select([4,2],4,[2])
    [12]       ┃       ┗ put([2],[4,1,3],[2,4,1,3])
    [13]       ┃         ┗ select([2],2,[])
    [14]>* 1:  ┗ e_register(0,N1_10,N1_10+1)    Try
 [14] 1 Try   : e_register(0,N1_10,N1_10+1) ?  
F
fail
【使用可能局面】「Try」「Match」「Retry」
「Try」「Retry」において使われた場合には、表示されたゴールと単一化可能な頭部を持つ節の有無に関わらず直ちに失敗させます。
「Match」の場合には、表示された節も含めて、ゴールと単一化可能な頭部を持つ節は1つもなかったかのように動作します。
H
help
それぞれの局面で使用可能なコマンドの表をコンソールに出力します。
I
inspect

該当ゴールを含む節の定義に変数があり、その変数が束縛されている、または制約されているときその情報を表示します。


値が束縛された変数 定義側変数名  =  束縛された値
制約変数 定義側変数名  =  制約変数名 {領域制約リスト :: 遅延実行ゴール }

なお、定義側変数が虚変数の場合、虚変数の出現順にシーケンシャル番号を付与し区別しています。


<例>
|?-listing.
turukame(Turu,Kame,Foot,Head) :-
    Turu in 0..Head,
    Kame in 0..Head,
    Foot#=Turu*2+Kame*4,
    Head#=Turu+Kame.
yes
|?-trace.
yes
||?-turukame(X,Y,14,5).
 [1] 0 Try   : turukame(X_7,Y_9,14,5) ?
  Match   : turukame(X_7,Y_9,14,5) :-
                X_7 in 0..5,
                Y_9 in 0..5,
                14#=X_7*2+Y_9*4,
                5#=X_7+Y_9.
 [2] 1 Try   : X_7 in 0..5 ? i           <== Iコマンド
        Foot     = 14                    <== 定義節の変数:Foot は 14に束縛されています
        Head     = 5                     <== 定義節の変数:Head は 5に束縛されています
 [2] 1 Try   : X_7 in 0..5 ?
 << BUILTIN CALL >>
 [2] 1 Succ  : _30 in 0..5
 [2] 1 Try   : Y_9 in 0..5 ? i           <== Iコマンド
        Turu     = _30 { [0..5]::true }  <== 定義節の変数:Turu は 0..5 に制約されています
        Foot     = 14
        Head     = 5
 [2] 1 Try   : Y_9 in 0..5 ?
 << BUILTIN CALL >>
 [2] 1 Succ  : _46 in 0..5
 [2] 1 Try   : 14#=_30*2+_46*4 ? i       <== Iコマンド
        Turu     = _30 { [0..5]::true }
        Kame     = _46 { [0..5]::true }
        Foot     = 14
        Head     = 5
 [2] 1 Try   : 14#=_30*2+_46*4 ?
 << BUILTIN CALL >>
 [2] 1 Succ  : 14#=_30*2+_46*4
 [2] 1 Try   : 5#=_30+_46 ? i            <== Iコマンド
        Turu     = _30 { [1,3,5]::$clp_area_propagation(7,[1,2],[_30,_46]),! }   <== 制約が狭まりました
        Kame     = _46 { [1..3]::$clp_area_propagation(7,[1,2],[_30,_46]),! }    <== 制約が狭まりました
        Foot     = 14
        Head     = 5
 [2] 1 Try   : 5#=_30+_46 ?
 <<BUILTIN CALL >>
 [2] 1 Succ  : 5#=3+2                    <== この制約で値が求まりました
 << LAST CALL >>
 [1] 0 Succ  : turukame(3,2,14,5)
X       = 3,
Y       = 2
yes
J
jump
【使用可能局面】「Try」「Match」
「Try」のとき、この述語を実行せずに決定性成功をします。
「Match」のとき、この節のボディーゴールを実行せずに成功します。
L
leap
スパイまたはマーク(Sコマンド参照)がかかっているゴールに関する処理をインタプリタが行うまでデバッガの表示を打ち切ります。スパイまたはマークをかけた述語に関する情報のみを表示させたいときには連続してこのコマンドを使います。
N
notrace
トレースまたはスパイポイントを解除します。ただしデバッグモードは解除されません。
O
step return
親スタックにマークを付け「Lコマンド」の処理を実行します。トレース中にステップを追わなくてもよい節に入り込んでしまったときに使います。
P
parent
先祖である節をすべて表示します。
(節1の本体にあるゴールと、単一化可能な頭部を持つ節2をインタプリタが見つけ、その実行に取り掛かったとします。そのようなとき、節1は節2の親であると呼びます。親および親の親、そのまた親の親・・・・を先祖と呼びます。)
<例>
| ?-[-'queen.pl'].
yes
| ?-spy select.
yes
debug mode on
||?-q(4).
 [4] 3 Try   : select([4,3,2,1],S_70,D_72) ? p  <== Pコマンドで先祖である節をすべて表示します
         3:  select([4,3,2,1],S_70,D_72)
         2:  put([4,3,2,1],[],L_8) :-           <== select/3の親はput/3です
             *> select([4,3,2,1],S_70,D_72),    <== ここをTry中
                safe([],S_70,S_70),
                put(D_72,[S_70],L_8).
         1:  queen(4,L_8) :-                    <== put/3の親はqueen/2です
                generate(4,[4,3,2,1]),
             *> put([4,3,2,1],[],L_8).          <== ここをTry中
         0:  q(4) :-                            <== queen/2の親はq/1です
                e_register(0,0,0),
             *> queen(4,L_8),                   <== ここをTry中
                e_register(0,N1_10,N1_10+1),
                M_12 is N1_10+1,
                write('No.'),
                write(M_12),
                nl,
                disp(L_8,4),
                fail.
         Goal:  ?- q(4).                        <== q/1を呼び出したトップレベルのゴールです
 [4] 3 Try   : select([4,3,2,1],S_70,D_72) ?
Q
mark&leap
【使用可能局面】「Try」「Retry」
スタックにマークを付け「Lコマンド」の処理を実行します。
R
redo
【使用可能局面】「Fail」
Failした該当ゴールを再Tryします。Failした原因を再検証したり、Breakしてプログラムを追加してから再実行させるなどの用途があります。
S
mark/unmark
【使用可能局面】「Try」「Retry」
スタックにマークを付けます。
既にマークがついているスタックではマークを削除します。
W
where
「Match」の場合には、Matchした述語定 義の位置(述語名/アリティ/マッチした節の順位)が表示されます。
Match以外の局面(Try,Retry,Succ,Fail,Pop)では、ゴールの位置(親述語名/親述語 アリティ/親述語の節順位/節中のゴール順位)が表示されます。
<例>
| a:- b.        % a/0/1  述語名=a,アリティ=0 ,第1節
| a:- c.         % a/0/2  述語名=a,アリティ=0 ,第2節

||?-a.
 [1] 0 Try   :a ?  
  Match   : a :-
                b. ? w  <== Wコマンド
      a/0/1      <== a/0 の第1節にマッチしました
 [2] 1 Try   :b ? w     <== Wコマンド
          a/0/1/1       <== a/0 の第1節の1番目のゴール(b)をTryしています

6-5-3.Dコマンドを使ったスタック状態の調べかた

Prologの理解を深めるため、前節で説明したDコマンドを使って、スタックの状態を追ってみます。


<例1>次のようなシンプルなプログラムで説明します。

%%%%%%%%%%%%%%%
%%  tree.pl  %%

a:-b,c.     % b かつ c が真ならば、a は真である。
b:-d,e.     % d かつ e が真ならば、b は真である。
e:-f.      % f が真ならば、e は真である。

d.       % d は真である。
d.       % d は真である。(オルタナティブ。下図では d'で表現)
f.       % f は真である。
f.       % f は真である。(オルタナティブ。下図では f'で表現)
c.       % c は真である。

このプログラムで a が真か調べる過程は次のように木構造の成長で表現できます。

?-a.

=STEP1================
0:     [1]a Try

=STEP2================
0:     [1]a
       /
1:    [2]b Try

=STEP3================
0:     [1]a
       /
1:    [2]b 
      /
2: [3]d(d') Try = Succ

=STEP4================
0:     [1]a
       /
1:    [2]b
      ∧
2: [3]d(d') [4]e Try

=STEP5================
0:     [1]a
       /
1:    [2]b
      ∧
2: [3]d(d') [4]e
       /
3:    [5]f(f') Try = Succ

=STEP6================
0:     [1]a
       /
1:    [2]b
      ∧
2: [3]d(d') [4]e  Succ
       /
3:    [5]f(f')

=STEP7================
0:     [1]a
       ∧
1:    [2]b [6]c Try <== (Fコマンドで強制的にFailさせてみます)
      ∧    
2: [3]d(d') [4]e  
       /    ┃バックトラック
3:    [5]f(f')┣━┛

=STEP8================
0:     [1]a
       /
1:    [2]b 
      ∧
2: [3]d(d') [4]e
       /
3:    [5]f' ReTry = Succ (オルタナティブがないのでスタックからPOPします)

=STEP9================
0:     [1]a
       /
1:    [2]b 
      ∧
2: [3]d(d') [4]e Succ (オルタナティブがないのでスタックからPOPします)

=STEP10================
0:     [1]a
       ∧
1:    [2]b [4]c Try = Succ (C/Rで次に進ませます)
      /
2: [3]d(d')

=================
yes

ここで、
[数値]は「シリアルナンバー」(インタプリテーションが始まってからの実際のゴールの起動順番)、縦座標の”数値:”はその行のゴールの「先祖系列の深さ」です。

プログラムは[1]->[2]->[3]->[4]->[5]->[6]の順に働き、[6]c Tryで強制的にFailすると辿った道筋の逆順、[6]->[5]->[4]->[3]->[2]->[1]の順にバックトラックします。

これをトレース過程にDコマンドを織り込みながらスタック状態を調べてみましょう。
Dコマンドの出力は木構造をCUIで表現するために上記のGUI木構造を左右反転し「シリアルナンバー」順に並べ、親子関係を罫線文字で接続しています。

| ?-[-'tree.pl'].
yes
| ?-leash2(2),trace.
yes
debug mode on
||?-a.
 [1] 0 Try   :  TopGoal  :a ?       <== 入力ゴール a をTryします
  Match   :  a/0/1  :              <== a はbとcが真であれば真です
        a :- 
                b,
                c.
 [2] 1 Try   :  a/0/1/1   :b ?      <== a/0アリティ/第1節の第1ゴール、b が真か調べます
  Match   :  b/0/1  :             <== b はdとeが真であれば真です
        b :- 
                d,
                e.
 [3] 2 Try   :  b/0/1/1   :d ? d     <== b/0アリティ/第1節の第1ゴール、d が真か調べます。#ここでDコマンド
     [1]   0:  a                   <== 現在ゴールの祖父です
     [2]   1:  ┗ b                  <== 現在ゴールの親です
     [3]>* 2:    ┗ d    Try       <== 現在ゴールはここです
 [3] 2 Try   :  b/0/1/1   :d ?
  Match   :  d/0/1  :              <== d は真です
        d.
 [3] 2 Succ  :  b/0/1/1   :d       <== d が真であるのが証明されました
 [4] 2 Try   :  b/0/1/2   :e ? d   <== b/0アリティ/第1節の第2ゴール、e が真か調べます。#ここでDコマンド
     [1]   0:  a                   <== 現在ゴールの祖父です
     [2]   1:  ┗ b                  <== 現在ゴールの親です
     [3]         ┣ d                <== 現在ゴールの兄です。オルタナティブがあるので、スタックに残っています
     [4]>* 2:    ┗ e    Try       <== 現在ゴールはここです
 [4] 2 Try   :  b/0/1/2   :e ?
  Match   :  e/0/1  :              <== e は f が真であれば真です
        e :- 
                f.
 [5] 3 Try   :  e/0/1/1   :f ? d     <== e/0アリティ/第1節の第1ゴール、fが真か調べます。#ここでDコマンド
     [1]   0:  a                    <== 現在ゴールの曽祖父です
     [2]   1:  ┗ b                <== 現在ゴールの祖父です
     [3]         ┣ d                <== 現在ゴールの叔父(父の兄)です。オルタナティブがあるので、スタックに残っています
     [4]   2:    ┗ e                <== 現在ゴールの親です
     [5]>* 3:      ┗ f    Try     <== 現在ゴールはここです
 [5] 3 Try   :  e/0/1/1   :f ?     
  Match   :  f/0/1  :              <== f は真です
        f.
 [5] 3 Succ  :  e/0/1/1   :f       <== f が真であるのが証明されました
 [4] 2 Succ  :  b/0/1/2   :e       <== e が真であるのが証明されました
 [2] 1 Succ  :  a/0/1/1   :b       <== b が真であるのが証明されました
 [6] 1 Try   :  a/0/1/2   :c ? d   <== a/0アリティ/第1節の第2ゴール、c が真か調べます。#ここでDコマンド
     [1]   0:  a                     <== 現在ゴールの親です
     [2]       ┣ b                 <== 現在ゴールの兄です。子にオルタナティブがあるので、スタックに残っています
     [3]       ┃ ┣ d              <== 現在ゴールの甥(兄の第1子)です。オルタナティブがあるので、スタックに残っています
     [4]       ┃ ┗ e              <== 現在ゴールの甥(兄の第2子)です。子にオルタナティブがあるので、スタックに残っています
     [5]       ┃   ┗ f            <== 現在ゴールの大甥(兄の第2子の子)です。オルタナティブがあるので、スタックに残っています
     [6]>* 1:  ┗ c    Try         <== 現在ゴールはここです
 [6] 1 Try   :  a/0/1/2   :c ? f   <==  #強制的にFailさせてみます
 [6] 1 Fail  :  a/0/1/2   :c        <==  c は偽となります
 [5] 3 Pop   :  e/0/1/1   :f        <==  バックトラックし、先ほどのf の真を取り消します
 [5] 3 Retry :  e/0/1/1   :f ? d    <==  f の再充足(オルタナティブ)を調べます。#ここでDコマンド
     [1]   0:  a
     [2]   1:  ┗ b
     [3]         ┣ d
     [4]   2:    ┗ e
     [5]>* 3:      ┗ f    Retry   <== 現在ゴールはスタックをPOPした位置です
 [5] 3 Retry :  e/0/1/1   :f ?
  Match   :  f/0/2  :
        f.
 [5] 3 Succ  :  e/0/1/1   :f
 << LAST CALL >>                   <== fが再充足しました
 [4] 2 Succ  :  b/0/1/2   :e       <== fにはオルタナティブがないので、スタックをPOPします
 [2] 1 Succ  :  a/0/1/1   :b       <== fの親 eにはオルタナティブがないので、スタックをPOPします
 [4] 1 Try   :  a/0/1/2   :c ? d   <== bがSuccしました
     [1]   0:  a                     <== 再び c のTryで#Dコマンド、スタックを調べます
     [2]       ┣ b
     [3]       ┃ ┗ d
     [4]>* 1:  ┗ c    Try
 [4] 1 Try   :  a/0/1/2   :c ?     <== c のTry中です、fおよびeのスタックはオルタナティブがないのでスタックから取り除かれています
  Match   :  c/0/1  :              <== こんどはC/Rコマンドで成功させます
        c.
 [4] 1 Succ  :  a/0/1/2   :c
 [1] 0 Succ  :  TopGoal  :a
yes

<例2>

次にもう少し具体的な例を見てみましょう。
1)バックトラックは「シリアルナンバー」を逆に辿ること、すなわち辿ってきた道筋を戻ることであるのを確認してください。
2)もともとオルタナティブがない組込述語(is/2など)ユーザー定義述語、再試行後にオルタナティブがなくなり決定性終了をしたユーザ定義述語はスタックに積まれない(バックトラックの対象から除かれる)ことを確認してください。

%%%%%%%%%%%%%
%% cost.pl %%

購入総額(Yen,[S|Summary]) :-              % 購入総額は商品代金+送料である
    商品代金(Goods,Summary),
    送料(Send,S),
    Yen is Goods+Send.

商品代金(Price+Cost,[Package,Grade]) :-   % 商品代金は商品価格+梱包費である
    商品価格(Price,Grade),
    梱包費(Cost,Package).

梱包費(Cost,Package) :-                   % 梱包費は梱包材費用のことである
    包装材費用(Cost,Package).

商品価格(1000,傷あり特価品).              % 商品は2種類ある
商品価格(4000,特選最上級品).

包装材費用(0,   新聞紙袋簡易包装).        % 包装材は2種類ある
包装材費用(100, ダンボール箱梱包).

送料(120,普通便).                         % 送料は2種類ある
送料(340,速達便).
yes
| ?-[-'cost.pl'].
yes
| ?-leash(trs),trace.
yes
debug mode on
||?-購入総額(Yen,S).
 [1] 0 Try   : 購入総額(Yen_5,S_7) ? 
  Match   : 購入総額(Yen_5,[S_11|Summary_13]) :-
		商品代金(Goods_15,Summary_13),
		送料(Send_17,S_11),
		Yen_5 is Goods_15+Send_17.
 [2] 1 Try   : 商品代金(Goods_15,Summary_13) ? d
     [1]   0:  購入総額(Yen_5,[S_11|Summary_13])
     [2]>* 1:  ┗ 商品代金(Goods_15,Summary_13)    Try
 [2] 1 Try   : 商品代金(Goods_15,Summary_13) ? 
  Match   : 商品代金(Price_21+Cost_23,[Package_25,Grade_27]) :-
		商品価格(Price_21,Grade_27),
		梱包費(Cost_23,Package_25).
 [3] 2 Try   : 商品価格(Price_21,Grade_27) ? d
     [1]   0:  購入総額(Yen_5,[S_11,Package_25,Grade_27])
     [2]   1:  ┗ 商品代金(Price_21+Cost_23,[Package_25,Grade_27])
     [3]>* 2:    ┗ 商品価格(Price_21,Grade_27)    Try
 [3] 2 Try   : 商品価格(Price_21,Grade_27) ? 
  Match   : 商品価格(1000,傷あり特価品).
 [3] 2 Succ  : 商品価格(1000,傷あり特価品) ?
 [4] 2 Try   : 梱包費(Cost_23,Package_25) ? d
     [1]   0:  購入総額(Yen_5,[S_11,Package_25,傷あり特価品])
     [2]   1:  ┗ 商品代金(1000+Cost_23,[Package_25,傷あり特価品])
     [3]         ┣ 商品価格(1000,傷あり特価品)
     [4]>* 2:    ┗ 梱包費(Cost_23,Package_25)    Try
 [4] 2 Try   : 梱包費(Cost_23,Package_25) ? 
  Match   : 梱包費(Cost_23,Package_25) :-
		包装材費用(Cost_23,Package_25).
 [5] 3 Try   : 包装材費用(Cost_23,Package_25) ? d
     [1]   0:  購入総額(Yen_5,[S_11,Package_25,傷あり特価品])
     [2]   1:  ┗ 商品代金(1000+Cost_23,[Package_25,傷あり特価品])
     [3]         ┣ 商品価格(1000,傷あり特価品)
     [4]   2:    ┗ 梱包費(Cost_23,Package_25)
     [5]>* 3:      ┗ 包装材費用(Cost_23,Package_25)    Try
 [5] 3 Try   : 包装材費用(Cost_23,Package_25) ? 
  Match   : 包装材費用(0,新聞紙袋簡易包装).
 [5] 3 Succ  : 包装材費用(0,新聞紙袋簡易包装) ?
 [4] 2 Succ  : 梱包費(0,新聞紙袋簡易包装) ?
 [2] 1 Succ  : 商品代金(1000+0,[新聞紙袋簡易包装,傷あり特価品]) ?
 [6] 1 Try   : 送料(Send_17,S_11) ? d
     [1]   0:  購入総額(Yen_5,[S_11,新聞紙袋簡易包装,傷あり特価品])
     [2]       ┣ 商品代金(1000+0,[新聞紙袋簡易包装,傷あり特価品])
     [3]       ┃ ┣ 商品価格(1000,傷あり特価品)
     [4]       ┃ ┗ 梱包費(0,新聞紙袋簡易包装)
     [5]       ┃   ┗ 包装材費用(0,新聞紙袋簡易包装)
     [6]>* 1:  ┗ 送料(Send_17,S_11)    Try
 [6] 1 Try   : 送料(Send_17,S_11) ? 
  Match   : 送料(120,普通便).
 [6] 1 Succ  : 送料(120,普通便) ? d 
     [1]   0:  購入総額(Yen_5,[普通便,新聞紙袋簡易包装,傷あり特価品])
     [2]       ┣ 商品代金(1000+0,[新聞紙袋簡易包装,傷あり特価品])
     [3]       ┃ ┣ 商品価格(1000,傷あり特価品)
     [4]       ┃ ┗ 梱包費(0,新聞紙袋簡易包装)
     [5]       ┃   ┗ 包装材費用(0,新聞紙袋簡易包装)
     [6]>* 1:  ┗ 送料(120,普通便)    Succ
 [6] 1 Succ  : 送料(120,普通便) ? 
 [7] 1 Try   : Yen_5 is 1000+0+120 ? 
 << BUILTIN CALL >>
 [7] 1 Succ  : 1120 is 1000+0+120 ? d
     [1]   0:  購入総額(1120,[普通便,新聞紙袋簡易包装,傷あり特価品])
     [2]       ┣ 商品代金(1000+0,[新聞紙袋簡易包装,傷あり特価品])
     [3]       ┃ ┣ 商品価格(1000,傷あり特価品)
     [4]       ┃ ┗ 梱包費(0,新聞紙袋簡易包装)
     [5]       ┃   ┗ 包装材費用(0,新聞紙袋簡易包装)
     [6]       ┣ 送料(120,普通便)                       <== 送料スタックありに注意
     [7]>* 1:  ┗ 1120 is 1000+0+120    Succ
 [7] 1 Succ  : 1120 is 1000+0+120 ? 
 [1] 0 Succ  : 購入総額(1120,[普通便,新聞紙袋簡易包装,傷あり特価品]) ? 

Yen	= 1120,                     <== 第1解
S	= [普通便,新聞紙袋簡易包装,傷あり特価品];    <== 別解要求の入力

 [6] 1 Pop   : 送料(120,普通便)               <== 送料の選択解除
 [6] 1 Retry : 送料(Send_17,S_11) ?              <== 送料の別可能性試行
  Match   : 送料(340,速達便).                 <== 再充足しました
 [6] 1 Succ  : 送料(340,速達便) ? 
 [6] 1 Try   : Yen_5 is 1000+0+340 ? 
 << BUILTIN CALL >>
 [6] 1 Succ  : 1340 is 1000+0+340 ? d
     [1]   0:  購入総額(Yen_5,[速達便,新聞紙袋簡易包装,傷あり特価品])
     [2]       ┣ 商品代金(1000+0,[新聞紙袋簡易包装,傷あり特価品])
     [3]       ┃ ┣ 商品価格(1000,傷あり特価品)
     [4]       ┃ ┗ 梱包費(0,新聞紙袋簡易包装)
     [5]       ┃   ┗ 包装材費用(0,新聞紙袋簡易包装)
     [6]>* 1:  ┗ Yen_5 is 1000+0+340    Try         <== 送料スタックなしに注意
 [6] 1 Try   : Yen_5 is 1000+0+340 ? 
 [1] 0 Succ  : 購入総額(1340,[速達便,新聞紙袋簡易包装,傷あり特価品]) ? 

Yen	= 1340,                     <== 第2解
S	= [速達便,新聞紙袋簡易包装,傷あり特価品]
yes

%% 第2解が求まった後、再度別解要求をすると包装材費用の別可能性を試行します
%% 次のような問い合わせをするとどのようなTraceになるか試し、なぜそうなるのか考えてください

||?-購入総額(Yen,[速達便,X,特選最上級品]).