为什么我的序言规则陷入无限递归

问题描述

我的代码可以达到预期的目的,但总是在最后陷入循环,给我一个错误提示“超出堆栈限制”。我的代码如下:

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):- byPlane(X,Y):- travel(X,Z),travel(Z,Y).

解决方法

当您调用类似travel(metz,To)之类的内容时,travel/2的最后一个子句将使用新变量travel(metz,Z)调用Z,然后可以使用以下变量调用travel(metz,Z2)一个新变量Z2,依此类推。

此问题称为“左递归”:您有一个递归调用,该调用等效于子句从头到尾(即从头开始)的原始目标。解决方案是在递归调用之前“取得一些进展”。在这种情况下,您可以在递归之前跳一跳:

step(X,Y) :-
    byCar(X,Y).
step(X,Y) :-
    byTrain(X,Y) :-
    byPlane(X,Y).

travel(X,Y) :-
    step(X,Y).
travel(X,Z) :-
    step(X,Y),travel(Y,Z).

此操作现在终止:

?- travel(metz,To).
To = frankfurt ;
To = paris ;
To = bangkok ;
To = singapore ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = auckland ;
To = hamilton ;
To = raglan ;
To = losAngeles ;
To = auckland ;
To = hamilton ;
To = raglan ;
false.

正如false的注释中指出的那样,您可以使用通用谓词来捕获这种闭合:Definition of Reflexive Transitive Closure。另外,某些Prolog系统提供了一个称为“ tabling”的功能,您可以使用它来避免这种问题:https://www.swi-prolog.org/pldoc/man?section=tabling-non-termination