更好地理解序言
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。它可能会到处找到解决方案,但当被要求找到所有解决方案时它永远不会终止。
这个小片段称为 failure-slice,如果它没有终止,您的整个程序也会终止!也就是说,根本不需要阅读你对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) 如果你有一个纯粹的单调程序。
我正在尝试了解 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。它可能会到处找到解决方案,但当被要求找到所有解决方案时它永远不会终止。
这个小片段称为 failure-slice,如果它没有终止,您的整个程序也会终止!也就是说,根本不需要阅读你对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) 如果你有一个纯粹的单调程序。