【问题标题】:Replacing prolog base case with recursive call用递归调用替换 prolog 基本情况
【发布时间】:2018-11-16 09:55:06
【问题描述】:

我是 Prolog 的新手,最近开始使用这本很棒的书 Learn Prolog Now! 学习它。有些东西我不完全理解,这真的让我很烦。 exercises 的问题之一是

我们有以下知识库和谓词:

child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).

descend(X,Y)  :-  child(X,Y).
descend(X,Y)  :-  child(X,Z),
                  descend(Z,Y).

如果我们将谓词更改为以下内容会发生什么:

descend(X,Y)  :-  child(X,Y).
descend(X,Y)  :-  descend(X,Z),
                  descend(Z,Y).

我知道这会导致错误情况下的无限递归,但我无法完全理解为什么。

如果我理解正确,在上面的第一种情况下,如果给出错误查询 child(X,Z) 将耗尽其所有选项,试图将多个元素统一到 Z 然后失败,回溯到前一个 X 然后再次尝试 Z 选项满足孩子(X,Z)。 (如果我错了,请纠正我)。

我不确定为什么对于descend 谓词的第二个定义不会发生同样的情况。

【问题讨论】:

    标签: recursion prolog


    【解决方案1】:

    让我们花点时间将您显示的 sn-p 简化为一个片段,它清楚地显示了未终止的原因

    初始片段是您发布的整个程序:

    下降(X,Y):- 孩子(X,Y)。 下降(X,Y):-下降(X,Z), 下降(Z,Y)。

    现在,我们在某些地方插入 false/0。例如:

    descend(X,Y) :- false, child(X,Y)。 下降(X,Y):-下降(X,Z), 下降(Z,Y)

    我正在使用 strikeout 文本来指示程序中对终止没有影响的部分。也就是说,我们实际上最终得到:

    下降(X,Y):-下降(X,Z)。

    仅此片段就已导致未终止。您没有添加任何纯子句,并且遵循单一目标的任何内容都无法阻止它!因此,要终止此操作,您必须更改此片段

    更多信息请参见

    【讨论】:

      【解决方案2】:

      出于说明目的,让我们考虑(a,b) 这对,因为您的事实 child/2 没有涵盖它。如果您发出查询...

      ?- descend(a,b).
      

      ...Prolog 将尝试descend/2 的第一个子句,替换为X=aY=b

      descend(a,b)  :-  child(a,b).
      

      ...失败了,因为目标child(a,b) 失败了。然后Prolog转到descend/2的第二个子句:

      descend(a,b) :-
      

      ...其中引入了一个新变量Z,并递归调用了descend/2:

      descend(a,b)  :-  descend(a,Z),
      

      Prolog 现在尝试descend/2 的第一个子句...

      descend(a,Z)  :-  child(a,Z).
      

      ... 失败了,因为目标 child(a,Z) 失败了。于是Prolog尝试了descend/2的第二个子句:

      descend(a,Z) :-
      

      ...这里是一个新变量,我们称它为 Z2(取决于您使用的 Prolog 系统,它的名称将类似于 _G3325 (SWI)、_A (YAP),...但是对于这个例子,让我们坚持使用更具说明性的 Z2) 被引入并调用递归目标:

      descend(a,Z)  :-  child(a,Z2).
      

      因为总是有一个新的变量可以被引入,所以上面的descend/2 的定义会循环直到你用完栈。通过类似的推理,您可以理解查询的原因...

      ?- descend(anne,bridget).
      true ;
      ERROR: Out of local stack
      
      ?- descend(X,Y).
      X = anne,
      Y = bridget ;
      X = bridget,
      Y = caroline ;
      X = caroline,
      Y = donna ;
      X = donna,
      Y = emily ;
      X = anne,
      Y = caroline ;
      X = anne,
      Y = donna ;
      X = anne,
      Y = emily ;
      ERROR: Out of local stack
      

      ...在产生答案后也循环。

      编辑:

      请参阅@mat 的出色答案,以更优雅的方式识别导致不终止的片段。

      【讨论】:

        【解决方案3】:

        更简单的方法来帮助你而不是在这里写一个冗长而详细的解释,只是一步一步地重现跟踪的运行,只是建议你使用内置在 SWI-Prolog 中的 graphical 跟踪并执行自己动手。

        在向您展示您应该对代码进行的一项更改之前,您应该重命名第二个示例的谓词,以便同时使用它们。

        child(anne,bridget).
        child(bridget,caroline).
        child(caroline,donna).
        child(donna,emily).
        
        descend(X,Y) :-
            child(X,Y).
        
        descend(X,Y) :-
            child(X,Z),
            descend(Z,Y).
        
        descend_2(X,Y) :-
            child(X,Y).
        
        descend_2(X,Y) :-
            descend_2(X,Z),
            descend_2(Z,Y).
        

        启动 SWI-Prolog

        加载您的源代码。我个人使用consult/1 例如

        consult("C:/Users/Eric/Documents/Projects/Prolog/SO_question_100.pl").
        

        gtrace/0打开图形跟踪器

        gtrace.
        

        输入您的查询,例如

        descend_2(anne,bridget).
        

        这将使用图形调试器启动第二个屏幕。

        在这一点上,我必须对如何使用图形调试器以及显示中所有不同项目的含义进行详细说明,但这里有一个值得注意的部分。我只需按几次空格单步即可到达它。继续此操作,直到听到提示音。当您听到提示音时,表示您需要切换到另一个屏幕并提供输入,在这种情况下,只需按空格键即可接受答案。然后用图形调试器切换回屏幕,继续按空格键单步。

        感兴趣的部分是右侧的堆栈。我在其中一个周围放了一个绿色框,上面显示了一个类似Y 的图标,它代表一个选择点。如果您继续使用空格键,您会注意到堆栈随着选择点的增加而不断增长。

        所以发生的事情是您的代码有选择,但选择没有得到解决。看看left recursionProlog/Recursive Rules

        【讨论】:

        • 谨防people提出问题,然后删除问题和答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多