【问题标题】:Stack Trace Prolog Predicate堆栈跟踪 Prolog 谓词
【发布时间】:2014-02-21 06:43:53
【问题描述】:

我有一个用 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: 1 之后会发生什么。为什么它在第 3 行调用第二种情况 min(E, [E|L]) 在转到第 1 种情况 min(E, [E]) 之后?从代码中的任何地方都不会出现这种情况。如果有人可以解释前两个电话之后发生的事情,那就太好了。我四处寻找一些解释,但我无法理解这里发生了什么。

【问题讨论】:

    标签: algorithm recursion prolog backtracking


    【解决方案1】:

    为了解决这个问题,我们将使用 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]),并且能够通过将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 =&lt; F,其中E2 统一,F1 统一。但随后E =&lt; F 将失败,因为1 =&lt; 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]) 统一(注意:这些是“不同的”F)将第一个 F1 统一,L[]E 与第二个 F 统一(但未实例化 - 未分配值)。这里需要注意的是,在 Prolog 中,两个变量可以统一但尚未赋值。由于第三条的头部已经统一,case 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, [E]) 和序言将 E1 并匹配子句的头部。然后我们看到:

    case 1: 1
    

    (I) 案例 1 成功,然后返回案例 3,继续检查 E =&lt; F 是否为 1 =&lt; 2(参见 (G) 中的统一),这是正确的。我们现在已经完全成功案例 3!

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

    E = 1.
    

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 我明白步骤是什么,但我不明白为什么。
      猜你喜欢
      • 1970-01-01
      • 2011-05-25
      • 2011-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-11
      • 2018-09-19
      相关资源
      最近更新 更多