如何让Prolog在真实陈述之外解释您的结果

如何让Prolog在真实陈述之外解释您的结果,prolog,Prolog,我有以下事实和规则: flight(sea,msp). flight(msp,jfk). route(A,B) :- flight(A,B). route(B,A) :- flight(A,B). route(A,C) :- flight(A,B) , flight(B,C). 当查询route(sea,jfk)时,我得到一个结果true,但我希望得到的是解释: sea-->msp-->jfk通过这种方式,我不仅可以判断它是真的,还可以判断它是怎么回事 这在很大程度上取决

我有以下事实和规则:

  flight(sea,msp).
  flight(msp,jfk).

 route(A,B) :- flight(A,B). 
 route(B,A) :- flight(A,B). 
 route(A,C) :- flight(A,B) , flight(B,C). 
当查询
route(sea,jfk)
时,我得到一个结果
true
,但我希望得到的是解释:


sea-->msp-->jfk
通过这种方式,我不仅可以判断它是真的,还可以判断它是怎么回事

这在很大程度上取决于prolog系统。由于您已将其标记为swi,我将给您一个特定于swi的答案

你可以启动追踪器。使用
跟踪/0

?: trace.
true

[trace]?:
现在输入查询时,可以看到谓词的所有调用、退出、失败和重做。但是,在命令行跟踪程序中看不到变量名。要查看可以采取的操作,可以键入
h
。最有趣的可能是下一步的
n
,以及完成当前目标的
f

或者也可以使用
trace/1
trace/2
输出调用堆栈的部分:

?: trace(flight/2). % calls, exits, fails and redos are output for flight/2

?: trace(route/2, +exit).  % only exits are output for route/2.
如果还安装了xpce,则可以使用
gtrace/0
作为图形界面

如果您想从prolog中访问路线,还可以编写一个新的
route/3
,它还可以输出路线列表

因此,对于您的案例,您可以执行以下查询:

?- trace(flight/2,+exit).
%         flight/2: [exit]
true.

[debug]  ?- route(sea,jfk).
 T Exit: (7) flight(sea, msp)
 T Exit: (7) flight(msp, jfk)
true.

跟踪图形中已访问的节点。无论如何,您都需要这样做,因为您需要在图形中检测循环,以免落入无限递归的兔子洞

在Prolog中,我们使用助手方法,将状态作为一个或多个额外参数进行传递。一个常用的约定是使用一个“公共”谓词,比如说
route/3
,它调用一个具有相同名称且具有较高算术性的“私有”工作者谓词,比如说
route/4
。像这样的事情应该对你有帮助:

route( A , B , R  ) :- % find a route R from A to B
  route(A,B,[],R)      % - by invoking a worker, seeding its list of visited nodes with the empty list
  .                    % Easy!

route(B,B,V,R) :-    % we've arrived at the destination (B) when the origination node is the same as the destination node.
  reverse([B|V],R)   % - just reverse the list of visited nodes to get the routing.
  .                  %
route(A,B,V,R) :-    % otherwise...
  flight(A,T) ,      % - if there's an edge out of the current node (A) ,
  \+ member(T,V) ,   % - to an as-yet unvisited node...
  route(T,B,[A|V],R) % - go visit that node, marking the current node as visited.
  .                  % Easy!

所以你想从
A
B
,但不仅如此,你还想知道你行程中的站点列表

请务必仔细阅读以下两个相关问题以及针对该问题提出的答案:


上面链接中提供的元谓词允许您将递归的处理委托给一个可靠的、经过测试的、可重用的组件。更多时间专注于解决问题的其他部分

如果您既不想使用调试,也不想使用helper谓词编写其他方法,第三种选择是利用SWI-Prolog的许多内置功能进行元编程。在这种情况下,可能会有帮助(它是ISO标准的一部分,所以其他Prolog方言也可能有,但我没有检查):

条款(:头、体)

如果
Head
可以与子句头统一,而
Body
可以与相应的子句体统一,则为True。提供关于回溯的替代条款。对于事实,
Body
与atom
true
相统一

因此,我们可以编写一个通用谓词
expl(+Goal,-expl)
,在
目标
成功的情况下,为
目标
构造一个“解释”:

flight(sea,msp).
flight(msp,jfk).

route(A,B) :- flight(A,B). 
route(B,A) :- flight(A,B). 
route(A,C) :- flight(A,B) , flight(B,C). 

% construct an explanation for a solution
expl([],[]).
expl([Goal|Goals],[(Goal,BodyExpl)|Expl]) :-
        clause(Goal,Body),
        clause_body_list(Body,BodyL),
        expl(BodyL,BodyExpl),
        expl(Goals,Expl).

% turn output of clause/2 into a list
clause_body_list(true,[]) :- !.
clause_body_list((A,B),[A|BL]) :- !,
        clause_body_list(B,BL).
clause_body_list(A,[A]) :- !.
这将产生:

?- expl([route(sea,jfk)],X).
X = [(route(sea, jfk), [(flight(sea, msp), []),  (flight(msp, jfk), [])])].
它还支持回溯和变量查询:

?- expl([route(A,B)],X).
A = sea,
B = msp,
X = [(route(sea, msp), [(flight(sea, msp), [])])] ;
A = msp,
B = jfk,
X = [(route(msp, jfk), [(flight(msp, jfk), [])])] ;
A = msp,
B = sea,
X = [(route(msp, sea), [(flight(sea, msp), [])])] ;
A = jfk,
B = msp,
X = [(route(jfk, msp), [(flight(msp, jfk), [])])] ;
A = sea,
B = jfk,
X = [(route(sea, jfk), [(flight(sea, msp), []),  (flight(msp, jfk), [])])] ;
false.
请注意,这样的解释不一定是列表,但通常采用(SLD)树的形式,因此是输出的嵌套结构

编辑:进一步解释上述内容:输出是一个“解释”列表,其形式为
(目标,BodyExpl)
,其中每个
目标
都是一个已被证明的(子)目标,
BodyExpl
再次是用于证明
目标
的所有递归子目标的此类解释列表。事实上,
BodyExpl
部分只是空的。通常,此结构可以嵌套任意深度(取决于您的输入程序)

如果您觉得这很难理解,对进一步处理输出不感兴趣,并且只需要可读的解释,您可以执行以下操作:

flight(sea,msp).
flight(msp,jfk).

route(A,B) :- flight(A,B). 
route(B,A) :- flight(A,B). 
route(A,C) :- flight(A,B) , flight(B,C). 

% construct an explanation for a solution
expl([]).
expl([Goal|Goals]) :-
        clause(Goal,Body),
        clause_body_list(Body,BodyL),
        expl(BodyL),
        expl(Goals),
        write_expl(Goal,Body).

% turn output of clause/2 into a list
clause_body_list(true,[]) :- !.
clause_body_list((A,B),[A|BL]) :- !,
        clause_body_list(B,BL).
clause_body_list(A,[A]) :- !.

% write explanation
write_expl(Goal, true) :- !,
        writef('%w is a fact.\n',[Goal]).
write_expl(Goal, Body) :- !,
        writef('%w because of %w.\n', [Goal,Body]).
例如,这将产生:

?- expl([route(sea,jfk)]).
flight(msp,jfk) is a fact.
flight(sea,msp) is a fact.
route(sea,jfk) because of flight(sea,msp),flight(msp,jfk).

请注意,只有在对
expl
进行递归调用之后,您才想调用
write_expl
,因为一些变量可能只在递归调用期间被实例化。

谢谢,我消除了路线(A,C):-flight(A,B),flight(B,C)。规则,因为您添加了它。有什么好的链接可以了解“调用工作程序”和列表吗?再次感谢。获得一份Sterling&Shapiro's的副本并完成它将是一个良好的开端。远比Clocksin&Mellish好(如果你问我的话)。另见Richard O'Keefe的,它不是介绍性文本。更多的是技巧和优雅。谢谢你,尼古拉斯,非常感谢!可移植性注意:大多数Prolog系统只允许在动态谓词上使用
子句/2