繁体   English   中英

堆栈跟踪Prolog谓词

[英]Stack Trace Prolog Predicate

我有一个用Prolog编写的非常简单的min函数,但我不明白它是如何工作的。

码:

min(E, [E]) :- write('case 1: '), write(E), nl.
min(E, [E|L]) :- write('case 2: '), write(E), write(' '), write([E|L]), nl, min(F, L), E =< F.
min(E, [F|L]) :- write('case 3: '), write(E), write(' '), write([F|L]), nl, min(E, L), E =< F.

我们刚刚在课堂上开始使用Prolog,但我不明白它如何评估像这样的递归案例。 我在此函数中包含了打印语句,以查看发生了什么,而我不理解此处的某些步骤:

10 ?- min(E, [2, 1]).
case 2: 2 [2,1]
case 1: 1
case 2: 1 [1]
case 3: _L164 [1]
case 3: _G323 [2,1]
case 1: 1
E = 1 .

我了解前两个电话,但不了解第case 1: 1case 1: 1之后发生的case 1: 1 为什么在第1行min(E, [E|L])之后在第3行调用第二种情况min(E, [E|L]) min(E, [E]) 这并不是从代码中的任何地方开始的。 如果有人可以解释在前两个电话之后发生了什么,那就太好了。 我到处寻找一些解释,但是我无法理解这里发生的事情。

为了弄清楚这一点,我们将播放序言解释器。 :)

min(E, [E]) :-
    write('case 1: '), write(E), nl.
min(E, [E|L]) :-
    write('case 2: '), write(E), write(' '), write([E|L]), nl,
    min(F, L),
    E =< F.
min(E, [F|L]) :-
    write('case 3: '), write(E), write(' '), write([F|L]), nl,
    min(E, L),
    E =< F.

我们进行查询:

min(E, [2,1]).

(A)Prolog从第一个子句min(E, [E]) ,但由于[2,1]无法与[E]统一而失败。 然后转到下一个子句min(E, [E|L]) ,通过将E2L[1]统一,可以将[2,1][E|L]统一,然后我们看到:

case 2: 2 [2,1]    % This is E instantiated as 2, and [E|L] as [2|[1]]

(B)然后,Prolog进行递归查询min(F, [1]) 从这里开始,它返回到子句列表的顶部(在新查询中,它从顶部开始),并且能够通过将F1统一来统一第一个子句min(E, [E])的变量。 然后,我们看到:

case 1: 1

(C)此查询成功,并返回到从其查询的子句,并遇到E =< F ,其中E统一为2F统一为1 但是然后E =< F将失败,因为1 =< 2是不正确的。 此时,Prolog将回溯并重新尝试它刚刚执行的先前递归查询min(F, [1]) 回想一下,该查询已经执行了第一个子句并成功执行,因此现在回溯将尝试第二个子句。 它看起来将min(F, [1])min(E, [E|L])统一,并且可以通过将E1L[]统一来实现。 然后执行子句2,我们得到:

case 2: 1 [1]

(D)我们现在在第2节中进行了另一个致电。我们还没有完成第一个电话。 因此,此新调用将查询min(F, []) (在这种情况下,请记住L[]统一)。 谓词中没有与min(F, [])匹配的子句,因此它失败。 因此,案例2查询的此实例完全失败(通过回writes进行回溯,而不会在回溯上重新执行)。 这是上面(C)中的递归查询。

(E)由于案例2在(C)的递归调用中失败,因此Prolog继续回溯并通过执行第三子句重新尝试,并将min(E, [F|L])min(F, [1])统一(注:通过将第一个F1统一,将L[]E统一为第二个F (但未实例化-未分配值),将它们“区别开”。 重要的是,在Prolog中,两个变量可以统一但尚未分配值。 由于第三个子句的标题已经统一,因此案例3会执行,我们会看到:

case 3: _L164 [1]    % This is E (uninstantiated) and [F|L] ([1|[]])

出现_L164原因是我们正在编写一个未实例化的变量。 未实例化的变量在这样的输出中显示为生成的变量名,并带有下划线( _ )。

(F)因此,情况3执行并递归调用min(E, L) ,其中E未实例化, L[] 该查询将失败,因为没有匹配min(_, [])子句。 然后,Prolog将从情况3回溯,然后从(C)到min(F, [1])的整个递归调用失败。

(G)请记住,在(C)中描述的情况2中,我们是通过递归调用到达(F)的。 由于该递归调用失败(如(D)到(F)中所述),Prolog通过回溯,使案例2失败并继续进行到案例3来恢复(C)中描述的案例2。整个谓词的执行是从原始查询min(E, [2,1]) 第三子句的开头是min(E, [F|L]) ,Prolog将第一个E与第二个E统一(但是未例示),将F2以及L[1]统一。 现在我们看到:

case 3: _G323 [2,1]   % This is E (uninstantiated) and [F|L] ([2|[1]])

(H)情况3继续进行,并对min(E, [1]) (具有用[1]实例化的L min(E, [1])进行递归查询,该查询再次从顶部开始,与第一个子句min(E, [E])和prolog匹配用1统一E并匹配子句的开头。然后,我们看到:

case 1: 1

(I)情况1成功,然后返回情况3,继续进行并检查E =< F1 =< 2 (请参阅(G)中的统一)是否为真。 现在,我们已经完全成功案例3!

我们完成了! 随着案例3的成功(案例1如(A)所述失败,案例2如(E)所述失败),原始查询通过将E1统一而成功完成,我们看到:

E = 1.

当您在Prolog中进行查询时,它将以您要查询的谓词的第一个子句开始,并按顺序尝试每个子句,直到找到成功的子句,然后声明成功。 如果它们全部失败,则查询当然会失败。 在尝试每个子句的过程中,如果存在一个递归查询(调用),则该递归调用将再次从第一个子句开始。 每个递归调用都是其对谓词的完整查询。 因此,每个递归调用都将从谓词的第一个子句开始,这是它自己通过每个谓词的子句寻求真相的过程。 这是了解Prolog的重要原则,这将有助于理解基本的递归行为。

关于跟踪的主题,代码中的write语句很好地显示了谓词fire的哪些子句。 但是它们没有显示子句中的哪些查询失败,因此在试图了解查询中发生的情况时知道这一点同样重要。 因此,仅write语句可能会使您感到困惑。 gtrace建议的gtrace (或trace )命令将显示成功和失败的查询。 这是一个很好的工具,可以用来查看子句中发生的事情,也许可以与write语句一起查看变量等。

您可以在SWI-Prolog中使用gtrace来跟踪评估。

10?-gtrace,min(E,[2,1])。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM