我正在处理我的第一个Prolog任务,并且在递归问题上,我似乎无法停止溢出堆栈。它就像一个上瘾;我不知道如何停下来。
让我举个例子。我想创建一个函数来确定一个对象Y是否是另一个对象X的一部分。这是我正在使用的数据库:
% Parts Database
has(bicycle,wheel,2).
has(bicycle,handlebar,1).
has(bicycle,brake,2).
has(wheel,hub,1).
has(wheel,spoke,32).
has(bicycle,frame,1).
has(car,steering_wheel,1).
has(car,stereo,1).
has(car,tires,4).
从这里开始,我想编写一个函数partof(X,Y)
,如果Y是X的一部分,则返回true。这就是我目前所拥有的:
% Determines if Y is a part of X
partof(X,Y) :-
has(X,Y,_).
partof(X,Y) :-
partof(X,Z),
partof(Z,Y).
这适用于所有true
个查询,但溢出堆栈而不是返回false.
例如:
?- partof(wheel, spoke).
true ;
ERROR: Out of local stack
?- partof(bicycle, spoke).
true ;
ERROR: Out of local stack
?- partof(wheel, X).
X = hub ;
X = spoke ;
ERROR: Out of local stack
我知道你们都在想着自己,#34;这个愚蠢的白痴,并不知道基本情况是什么。我不知道如何退出递归。"好吧,我不怪你。我感到愚蠢。但是,一些患者指导会非常有帮助。提前谢谢!
答案 0 :(得分:3)
Prolog中的递归类似于其他编程语言,但它在Prolog中要复杂得多,因为在同一个运行中有两个独立的控制流交织在一起。在传统的命令式语言中,递归工作(并产生结果)或者不会因此循环,因此可能会溢出某些堆栈。
但是在Prolog中,我们可能首先得到许多有趣的答案,并且只有一段时间后我们才会陷入无限循环。您的查询非常幸运,您在第一个解决方案/答案后立即找到了循环。想象一下,您将采取以下查询:
?- partof(X, Y).
X = bicycle,
Y = wheel
; X = bicycle,
Y = handlebar
; X = bicycle,
Y = brake
; X = wheel,
Y = hub
; X = wheel,
Y = spoke
; X = bicycle,
Y = frame
; X = car,
Y = steering_wheel
; X = car,
Y = stereo
; X = car,
Y = tires
; X = bicycle,
Y = hub
; X = bicycle,
Y = spoke
;
ERROR: Out of local stack
请注意,解决方案并不是我们真正感兴趣的。我们只是在最后一条消息之后。在我们说查询终止之前,我们会打多少次?幸运的是,有一个更便宜的出路。我们只是添加 false
(一种永远不会成立的条件)作为额外目标。显然,查询必须永远不会有解决方案。剩下的唯一有趣的事情是查询是否终止:
?- partof(X, Y), false. ERROR: Out of local stack
这个技巧可以扩展到整个程序 1 。只需在您喜欢的位置添加 false
即可。剩下的程序称为失败切片与原始程序的共享仍然很多:它需要更少(或相等)的许多推断才能执行。因此,如果失败切片循环,则原始程序也循环。以下是程序的最小循环失败切片:
partof(X,Y) :- false,has(X,Y,_). partof(X,Y) :- partof(X,Z), false,partof(Z,Y).
请注意,has/3
已完全取消。换句话说,修改has/3
无论如何都不会停止这个循环!决不!您需要先修改可见部分中的某些内容。剩下的不多了!
经验丰富的Prolog程序员会立即看到这些问题。在failure-slice的帮助下,您可以学习为自己识别这些部分。而且,相信我,对于非常复杂的程序,总是可以选择检查你的直觉是否正确是非常有用的。
以下是使用closure/3
和library(lambda)
定义传递闭包的另一种方法:
partof(X, Y) :-
closure(\A^B^has(A, B,_), X, Y).
1实际上,这仅适用于纯粹的单调程序