Prolog递归过程的解释

时间:2013-01-30 07:26:45

标签: prolog failure-slice successor-arithmetics

如果可能的话,我希望有人解释这个程序(来自“现在学习prolog”一书)。它需要两个数字并将它们加在一起。

add(0,Y,Y).  
add(s(X),Y,s(Z)) :- add(X,Y,Z).

原则上我理解,但我有一些问题。让我说我发出查询

?- add(s(s(0)), s(0), R).

结果是:

R = s(s(s(0))).

步骤1是与规则2的匹配。现在X变为s(0)而Y仍然是s(0)。但是,Z(根据书中)变为s(_G648)或s(),其中包含一个未实例化的变量。为什么是这样?

在最后一步,第一条规则匹配,结束递归。在这里,Y的内容不知何故最终出现在Z的未实例化部分!非常困惑,我需要一个简单的英语解释。

3 个答案:

答案 0 :(得分:2)

第一个前提:

  • 我们将s(X)定义为X的后继者,所以基本上是s(X)= X + 1
  • _G ###表示法用于跟踪用于递归的内部变量

让我们首先看一下另外一个带有后继的加法定义,我发现它更直观:

add(0,Y,Y).
add(s(A),B,C) :- add(A,s(B),C).

这基本相同,但递归更容易看到:

我们问

add(s(s(0)),s(0),R).

现在,第一步prolog说相当于

add(s(0),s(s(0)),R)

因为我们有add(s(A),B,C) :- add(A,s(B),C),如果我们看问题A = s(0)和B = s(0)。但是这仍然没有终止,所以我们必须重新应用A = 0和B = s(s(0))的等价,所以它变成

add(0,s(s(s(0))),R)

,给定add(0,Y,Y).这意味着

R = s(s(s(0)))

你对add的定义基本上是相同的,但有两个递归:

首先它将第一个参数运行到0,所以它归结为添加(0,Y,Y):

add(s(s(0)),s(0),R)

X = s(0),Y = s(0)和s(Z)= R和Z = _G001

add(s(0),s(0),_G001)

X = 0,Y = s(0)和s(s(Z))= s(G_001)= R和Z = _G002

add(0,s(0),_G002)

所以现在它知道_G002是定义add(0,Y,Y)的s(0)但是必须追溯它的步骤,所以_G001是s(_G002)而R是s(_G001)是s(s) (_G002))是s(s(s(0)))。

所以重点是为了得到定义add(0,Y,Y)prolog必须为第一次递归引入内部变量,然后从第二次递归中计算R。

答案 1 :(得分:1)

如果您想了解Prolog程序的含义,您可能首先关注关系所描述的。然后你可能想了解它的终止属性。

如果您按照问题的建议深入了解具体执行的细节,您很快就会迷失在众多细节中。毕竟,Prolog有两个不同的隔行扫描控制流(AND-和OR-控制),除此之外,它还具有包含参数传递,赋值,比较和方程求解的统一。

简介:虽然计算机可以毫不费力地执行具体的查询以进行数以万计的推论,但在屏幕显示之后,您会感到疲倦。你无法击败计算机。幸运的是,有更好的方法来理解一个程序。

有关含义,请先查看规则。它写着:

add(s(X),Y,s(Z)) :- add(X,Y,Z).

请参阅中间的:-?它意味着象征箭头。箭头从右到左指向有点不寻常。在非正式的写作中,你会把它写成从左到右。请阅读如下:

  

如果add(X,Y,Z)为真,那么add(s(X),Y,s(Z))也是如此。

所以我们假设我们已经有一些add(X,Y,Z)意思是“X + Y = Z”。鉴于此,我们可以得出结论,“(X + 1)+ Y =(Z + 1)”也成立。

之后您可能有兴趣了解它的终止属性。让我简单地说一下:要理解它,只需看看规则即可:第二个论点只是进一步说明。因此:第二个参数不影响终止。第一和第三个参数看起来都一样。因此:它们都以同样的方式影响终止! 实际上,如果第一个或第三个参数不与s(_)统一,则添加/ 3终止。

在标记为的其他答案中详细了解该内容,例如:

Prolog successor notation yields incomplete result and infinite loop


但现在回答add(s(s(0)), s(0), R).的问题我只看第一个论点:是的!这将终止。就是这样。

答案 2 :(得分:0)

让我们将问题分为三个部分:关于实例化变量累加器模式的问题,我在该示例的变体中使用了这些问题:

add(0,Y,Y).                    
add(s(X),Y,Z):-add(X,s(Y),Z).

以及关于使用替换组合的示例的评论。

Prolog应用什么来查看哪个规则(即Horn子句)匹配(其头部统一)是Unification Algorithm,它特别告诉我,如果我有一个变量,让我们说,X和一个funtor ,即f(Y)这两个术语统一(关于occurs check有一小部分要...检查但是没关系atm)因此有一个替换可以让你转换一个到另一个。

当你的第二个规则被调用时,R确实统一为s(Z)。不要害怕Prolog给新的,未实例化的变量赋予的内部表示,它只是一个变量名称(因为它以'_'开头)代表一个代码(Prolog必须有一种方法来表达不断新生成的变量和所以_G648,_G649,_G650等)。 当您调用Prolog过程时,您传递的未经实例化的参数(在本例中为R)用于包含过程完成执行时的结果,并且它将包含结果,因为在过程中的某个时刻调用它将立即实现某些目标(始终通过统一)。 如果在某些时候你有一个var,即K被认为是s(H)(或s(_G567),如果你愿意的话),它仍然被部分地实例化并且你需要递归地实例化你的完整输出。 要查看它将被实例化的内容,请在累加器模式段落和后续段落中读取,以便处理问题。

累加器模式取自函数式编程,简而言之,是一种有变量的方法, accumulator (在我的情况下是Y本身),在一些过程调用之间进行部分计算的负担。该模式依赖于递归,并且大致具有以下形式:

  • 递归的基本步骤(我的第一个规则ie)始终表示,既然已经到达计算结束,您可以将累加器变量的部分结果(现在为总计)复制到输出变量(这是步骤其中,通过统一,您的输出变量得到实例化!)
  • 递归步骤告诉如何创建部分结果以及如何将它存储在累加器变量中(在我的情况下,我'增加'Y)。请注意,在递归步骤中,输出变量永远不会更改。

最后,关于你的例子,它遵循另一种模式,替换的组合,我认为你可以更好地理解累积器和通过统一实例化。

  • 它的基本步骤与累加器模式相同,但Y在递归步骤中从不改变,而Z确实
  • 用于通过在到达基本步骤并且每个过程调用结束后部分实例化每个递归调用结束时的所有计算,将Z中的变量与Y统一。所以在第一次调用结束时,Z中的内部自由变量已经被Y中的值多次统一代替。 注意下面的代码,在到达底部调用之后,过程调用堆栈开始弹出,部分变量(S1,S2,S3为简洁性)统一直到R完全实例化

这是堆栈跟踪:

add(s(s(s(0))),s(0),S1).          ^ S1=s(S2)=s(s(s(s(0))))
add(  s(s(0)) ,s(0),S2).          | S2=s(S3)=s(s(s(0)))
add(    s(0)  ,s(0),S3).          | S3=s(S4)=s(s(0))
add(      0   ,s(0),S4).          | S4=s(0)
add(      0   ,s(0),s(0)).  ______|