我是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)。 (如果我错了,请纠正我)。
我不确定为什么对于降级谓词的第二个定义不会发生同样的事情。
答案 0 :(得分:5)
让我们花点时间将您显示的片段缩小为片段,该片段清楚地显示出非终止的原因。
最初的片段是您发布的整个程序:
descend(X,Y) :- child(X,Y). descend(X,Y) :- descend(X,Z), descend(Z,Y).
现在,我们在某些地方插入 false/0
。例如:
descend(X,Y) :- false, child(X,Y). descend(X,Y) :- descend(X,Z), false,descend(Z,Y).
我正在使用 strikeout 文本来表示程序的某些部分,这些部分对终止没有影响。也就是说,我们实际上最终得到:
descend(X,Y) :- descend(X,Z).
仅此片段已导致终止。。您无需在其中添加任何纯子句,遵循单个目标的任何事物都不能阻止它!因此,要终止此操作,您必须更改此片段。
有关更多信息,请参见failure-slice。
答案 1 :(得分:3)
出于说明目的,让我们考虑(a,b)
对,因为事实不包括child / 2。如果您发出查询...
?- descend(a,b).
... Prolog将尝试使用下降{/ 2}的第一个子句,并用X=a
和Y=b
替换:
descend(a,b) :- child(a,b).
...并失败,因为目标child(a,b)
失败。然后Prolog移至下降/ 2的第二个子句:
descend(a,b) :-
...,其中引入了新变量Z
,并递归调用了下降/ 2:
descend(a,b) :- descend(a,Z),
Prolog现在尝试下降/ 2的第一子句...
descend(a,Z) :- child(a,Z).
...并失败,因为目标child(a,Z)
失败。因此,Prolog尝试下降/ 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的出色答案,以更优雅的方式识别导致终止的片段。
答案 2 :(得分:1)
一种简单的方法来帮助您,而不是在这里写一个冗长而详细的说明,而只是一步一步地复制跟踪的运行,而是建议您使用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 recursion和Prolog/Recursive Rules