我目前正在研究Learn Prolog Now示例,对于一个exercise,如果我在一个规则中只有一个微小的变化,那么我有一个用完本地堆栈的KB。这是KB:
byCar(auckland,hamilton).
byCar(hamilton,raglan).
byCar(valmont,saarbruecken).
byCar(valmont,metz).
byTrain(metz,frankfurt).
byTrain(saarbruecken,frankfurt).
byTrain(metz,paris).
byTrain(saarbruecken,paris).
byPlane(frankfurt,bangkok).
byPlane(frankfurt,singapore).
byPlane(paris,losAngeles).
byPlane(bangkok,auckland).
byPlane(singapore,auckland).
byPlane(losAngeles,auckland).
travel(X,Y) :- byCar(X,Y).
travel(X,Y) :- byTrain(X,Y).
travel(X,Y) :- byPlane(X,Y).
和相关规则:
travel(X,Y) :- travel(X,Z), travel(Z,Y).
这是有问题的查询,它用尽了堆栈:
?- travel(valmont,losAngeles).
但是,如果我将规则更改为
travel(X,Y) :- travel(Z,Y), travel(X,Z).
然后它工作。
如果我跟踪查询,我会很快被卡住:
Redo: (17) travel(raglan, _6896) ? creep
Call: (18) byPlane(raglan, _6896) ? creep
Fail: (18) byPlane(raglan, _6896) ? creep
Redo: (17) travel(raglan, _6896) ? creep
Call: (18) travel(raglan, _6896) ? creep
Call: (19) byCar(raglan, _6896) ? creep
Fail: (19) byCar(raglan, _6896) ? creep
Redo: (18) travel(raglan, _6896) ? creep
Call: (19) byTrain(raglan, _6896) ? creep
Fail: (19) byTrain(raglan, _6896) ? creep
Redo: (18) travel(raglan, _6896) ? creep
Call: (19) byPlane(raglan, _6896) ? creep
Fail: (19) byPlane(raglan, _6896) ? creep
Redo: (18) travel(raglan, _6896) ? creep
...
但我不明白为什么。难道它不应该只是理解插肩是一个终端,因此它必须更多地回溯一个级别?
谢谢!
编辑:我使用SWI Prolog
编辑:我在逐步完成后发现了问题。在插肩布的情况下,根本没有任何规则。因此,在尝试byPlane, byTrain, byCar
之后,它再次尝试travel(raglan, X)
(最后一条规则的第一个目标),从而循环。但我不明白其他规则是如何更好的。
显然,在这种情况下,目标排序非常重要。正如你所想的那样,你的第一个公式允许通过一个假想的另一个城市Z来寻找从插肩到任何地方的另一个假设连接,其中的不存在从未得到证实,因为你一直在寻找它无限递归。真的,这里的痕迹是你最好的朋友,但要做到这一点并非易事。您还必须考虑所有情况,其中一个,两个或没有一个参数被绑定。
你的第二个表述根本不是更好,它恰好在不同情况下失败:
travel(losAngeles, valmont).
ERROR: Out of local stack
我建议通过区分直接连接和多站点旅程来使您的逻辑更安全:
connection(X,Y) :- byCar(X,Y).
connection(X,Y) :- byTrain(X,Y).
connection(X,Y) :- byPlane(X,Y).
travel(X,Y) :- connection(X,Y).
travel(X,Y) :- connection(X,Z), travel(Z,Y).
目标顺序现在无关紧要,因为travel
总是需要一些物理连接才能存在(而不是递归)才能继续。
这也可以让你更容易记录你想要的旅程(对吧?):
connection(X,Y, car(X,Y)) :- byCar(X,Y).
connection(X,Y, train(X,Y)) :- byTrain(X,Y).
connection(X,Y, plane(X,Y)) :- byPlane(X,Y).
travel(X,Y,[Part]) :- connection(X,Y,Part).
travel(X,Y,[Part|Parts]) :- connection(X,Z,Part), travel(Z,Y,Parts).
?- travel(valmont, losAngeles, Journey).
Journey = [car(valmont, saarbruecken), train(saarbruecken, paris), plane(paris, losAngeles)]
对于没有有效旅行的情况:
travel(losAngeles, valmont, Journey).
false.
你需要澄清“它有效”的含义。实际上,谓词travel/2
的两个版本都不会终止。但有人碰巧为高度特定的查询找到了解决方案。
现在问?- travel(hamilton, losAngeles).
哪个循环。
因此,您的修复仅适用于某些查询,但不适用于其他查询。难道没有更可靠的出路吗?
通常,Prolog产生的非常精确的答案替换序列很难预测。你将不得不模拟Prolog所采取的每一个小步骤。
另一方面,有一个非常相关的概念称为(通用)终止,它更容易预测,因为它独立于程序中的许多细节,如事实的显示顺序。查询通用终止的最简单方法是在查询结尾添加目标false
。
但你甚至可以在任何地方进一步增加目标false
1。这样一个修改过的程序叫做failure-slice。无论你如何插入false
,以下内容如下:
如果failure-slice没有终止,那么原始程序也不会终止。
现在考虑travel/2
的两个变体的失败切片:
travel(X,Y) :- false, byCar(X,Y).travel(X,Y) :- false, byTrain(X,Y).travel(X,Y) :- false, byPlane(X,Y). travel(X,Y) :- travel(X,Z), false,travel(Z,Y).
而你的其他版本:
travel(X,Y) :- false, byCar(X,Y).travel(X,Y) :- false, byTrain(X,Y).travel(X,Y) :- false, byPlane(X,Y). travel(X,Y) :- travel(Z,Y), false,travel(X,Z).
在两者中,X
和Y
都没有被考虑过!所以这两个论点不影响终止。因此两个版本都不会终止。也就是说,它们永远不会终止。
现在将这个结论与更传统的追踪方法进行比较。虽然故障切片允许我们做出一般性结论(“......永不终止”),但特定的跟踪只能向您显示一个特定执行的详细信息。
为了解决这个问题,您需要更改可见部分中的内容。我的建议是使用closure/3
。那是:
travel(X, Y) :-
closure(connexion, X, Y).
connexion(X,Y) :- byCar(X,Y).
connexion(X,Y) :- byTrain(X,Y).
connexion(X,Y) :- byPlane(X,Y).
或者使用更一般的path/4
。
1实际上,这只适用于纯粹的单调程序。你的计划就是其中之一