更好地理解序言

Understanding prolog better

我正在尝试了解 Prolog 及其使用的解析算法。我找到了这个例子:

hates(1, 2).
hates(2, 3).
hates(3, 4).
jealous(A, B) :- jealous(A, C), jealous(C,B).
jealous(A,B) :- hates(A,B).

但是当我尝试说 jealous(1,4) 时,它一直溢出并且永远不会产生 true,这很奇怪,好像 1 讨厌 2,2 讨厌 3,3 讨厌 4,那么 1 也应该讨厌 4。

但是我试过改成这样:

hates(1, 2).
hates(2, 3).
hates(3, 4).
jealous(A,B) :- hates(A,B).
jealous(A, B) :- jealous(A, C), jealous(C,B).

然后当我说 jealous(1,4) 时它起作用了。

如果您使用的是 SWI-Prolog 或 XSB Prolog,您可以尝试添加:

:- table jealous/2.

它应该可以工作(我没试过这个 - 但这里有一个类似的例子:https://www.swi-prolog.org/pldoc/man?section=tabling-non-termination)。

你的问题是Prolog默认的执行策略,从左到右深度优先。您可以通过将最后一个子句更改为:

来避免这种情况
jealous(A, B) :- hates(A, C), jealous(C,B).

(Prolog教材中有很多类似的例子)

And then it works when I say jealous(1,4)

不,它没有。或者,好吧,有点。只需键入 SPACE 即可看到它再次循环。那么 jealous(4,1) 会立即循环吗?

要理解这一点,只需查看程序的一小部分即可,即:

jealous(A,B) :- false, hates(A,B).
jealous(A, B) :- jealous(A, C), false, jealous(C,B).

注意可见部分从未使用过的变量B。所以它不会对终止产生任何影响。并注意刚刚交给第一个目标的 A。所以这两个参数对这个程序没有影响。因此这个程序终止 never。它可能会到处找到解决方案,但当被要求找到所有解决方案时它永远不会终止。

这个小片段称为 ,如果它没有终止,您的整个程序也会终止!也就是说,根本不需要阅读你对hates/2的定义1.

如果你想掌握 Prolog 的执行,你需要掌握失败切片的概念。因为,有经验的程序员或多或少是凭直觉来做这件事的。因此,他们不会阅读整个程序,他们只是扫描相关部分。 Prolog 中的好处在于,这样的切片与整个程序之间存在真正的因果关系。

要解决这个问题,您需要在失败切片突出显示的部分中进行一些更改。这是一个可能的变化:

jealous(A,B) :- hates(A,B).
jealous(A, B) :- hates(A, C), jealous(C,B).

但是一旦你在 hates/2 中有了循环,这就不再有效了。然后,考虑 closure/3:

jealous(A, B) :-
   closure(hates, A, B).

另一种方法是使用表格。但请注意,制表解决了这个问题,但在约束的情况下将不再起作用(你迟早会遇到)。


1) 如果你有一个纯粹的单调程序。