Prolog不';目标重新排序后,不能终止

Prolog不';目标重新排序后,不能终止,prolog,backtracking,failure-slice,non-termination,Prolog,Backtracking,Failure Slice,Non Termination,我目前正在研究Learn Prolog Now示例,对于其中一个示例,如果一个规则中有一个小小的更改,我的KB将耗尽本地堆栈。这是知识库: byCar(auckland,hamilton). byCar(hamilton,raglan). byCar(valmont,saarbruecken). byCar(valmont,metz). byTrain(metz,frankfurt). byTrain(saarbruecken,frankfurt). byTrain(metz,pa

我目前正在研究Learn Prolog Now示例,对于其中一个示例,如果一个规则中有一个小小的更改,我的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
...
但我不明白为什么。难道它不应该理解raglan是一个终端站,因此它必须再回溯一层吗

谢谢

编辑:我使用SWI Prolog

编辑:我一步一步地检查后发现了问题。 在拉格伦的案例中,根本没有任何地方的规则。因此,在尝试乘飞机、乘火车、乘汽车后,它再次尝试旅行(raglan,X)(上一条规则的第一个目标),从而循环。
但我看不出另一条规则有什么更好的地方。

显然,在这种情况下,目标排序非常重要。正如你所发现的,你的第一个公式允许通过假设的另一个城市Z找到另一个从raglan到任何地方的假设连接,它的不存在永远不会被证明,因为你一直在寻找它,它会无限递归。说真的,trace是你最好的朋友,但这不是一件小事。您还必须考虑所有情况,其中一个、两个或没有一个参数是绑定的

您的第二个公式一点也不好,只是在不同的情况下碰巧失败了:

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(losAngeles, valmont, Journey).
false.

显然,在这种情况下,目标排序非常重要。正如你所发现的,你的第一个公式允许通过假设的另一个城市Z找到另一个从raglan到任何地方的假设连接,它的不存在永远不会被证明,因为你一直在寻找它,它会无限递归。说真的,trace是你最好的朋友,但这不是一件小事。您还必须考虑所有情况,其中一个、两个或没有一个参数是绑定的

您的第二个公式一点也不好,只是在不同的情况下碰巧失败了:

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(losAngeles, valmont, Journey).
false.

你需要澄清你所说的“它起作用”是什么意思。实际上,谓词
travel/2
的两个版本都没有终止。但我们碰巧找到了一个针对高度特定查询的解决方案

现在询问
?-travel(汉密尔顿,洛杉矶)。
哪一个循环对两者都适用

因此,您的修复只适用于某些查询,而不适用于其他查询。难道没有更可靠的出路吗

一般来说,Prolog产生的非常精确的答案替换序列很难预测。您必须模拟Prolog所采取的每一小步

另一方面,有一个非常相关的概念叫做(通用)终止,它更容易预测,因为它独立于程序中的许多细节,比如事实出现的顺序。查询通用终止的最简单方法是在查询末尾添加目标
false

但是你可以在任何你想要的地方进一步添加目标。这样一个修改过的程序称为。无论您如何插入
false
以下内容均有效:

如果故障片没有终止,那么您的原始程序也不会终止

考虑以下两个变体的代码片段:代码>旅行/ 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). 或者使用更一般的



1实际上,这只适用于纯单调程序。你的程序就是其中之一,你需要澄清你所说的“它有效”是什么意思。实际上,谓词
travel/2
的两个版本都没有终止。但我们碰巧找到了一个针对高度特定查询的解决方案

现在询问
?-travel(汉密尔顿,洛杉矶)。
哪一个循环对两者都适用

因此,您的修复只适用于某些查询,而不适用于其他查询。难道没有更可靠的出路吗

一般来说,Prolog产生的非常精确的答案替换序列很难预测。您必须模拟Prolog所采取的每一小步

另一方面,有一个非常相关的概念叫做(通用)终止,它更容易预测,因为它独立于程序中的许多细节,比如事实出现的顺序。查询通用终止的最简单方法是在查询末尾添加目标
false

但是你可以在任何你想要的地方进一步添加目标。这样一个修改过的程序称为。无论您如何插入
false
以下内容均有效:

如果故障片没有终止,那么您的原始程序也不会终止

考虑以下两个变体的代码片段:代码>旅行/ 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). 或者使用更一般的



1实际上,这只适用于纯单调程序。您的程序是非常有意义的程序之一!这也很好地进入了下一章