Stack Trace Prolog Predicate

时间:2014-02-21 06:43:53

标签: algorithm recursion prolog backtracking

我有一个用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,我不明白它是如何评估像这样的递归案例。我在这个函数中包含了print语句,看看发生了什么,我不明白这里的一些步骤:

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: 1之后会发生什么。在转到案例1 min(E, [E|L])后,为什么在第3行调用第二个案例min(E, [E])?这并不是代码中的任何地方。如果有人能够解释前两次通话后发生的事情,那就太棒了。我四处寻找一些解释,但我无法理解这里发生了什么。

2 个答案:

答案 0 :(得分:2)

为了解决这个问题,我们将扮演prolog口译员。 :)

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]),并通过[2,1][E|L]E统一2,将L[1]统一起来case 2: 2 [2,1] % This is E instantiated as 2, and [E|L] as [2|[1]] 然后我们看到:

min(F, [1])

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

case 1: 1

(C)此查询成功并返回到查询的子句并遇到E =< F E2统一,F1统一{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])统一起来(注意:这些是&# 34;不同的&#34; F&#39;通过将第一个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统一起来{ {1}} L。我们现在看到:

[1]

(H)案例3进行并对case 3: _G323 [2,1] % This is E (uninstantiated) and [F|L] ([2|[1]]) (已实例化min(E, [1]) L)进行递归查询,该查询再次从顶部开始,匹配第一个句子{{1} }和prolog将[1]min(E, [E])统一起来并匹配条款的头部。然后我们看到:

E

(I)案例1成功,并返回到案例3,案例3继续并检查1 case 1: 1 (见(G)中的统一),这是真的。我们现在完全成功了案例3!

我们已经完成了!随着案例3的成功(案例1如(A)中所述失败,案例2如(E)中所述失败),原始查询成功地将E =< F1 =< 2统一起来,我们看到了:

E

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

关于跟踪主题,代码中的1语句可以很好地显示谓词触发的子句。但他们没有显示条款中的哪些查询失败,这在了解何时尝试了解查询中发生的情况时同样重要。因此,仅使用E = 1. 语句就会有点混乱。 @User建议的write(或write)命令将显示成功和失败的查询。它是一个很好的工具,用于查看子句中发生的事情,可能与gtrace语句一起查看变量等。

答案 1 :(得分:0)

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

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