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

How to get Prolog to explain your result beyond the true statement

我有以下事实和规则:

  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 这样我不仅可以判断它是真的,而且可以判断它是怎么回事。

这在很大程度上取决于您的序言系统。由于您已将其标记为 swi,因此我将为您提供 SWI 特定的答案。

您可以启动追踪器了。随着 trace/0:

?: trace.
true

[trace]?:

当您现在输入一个查询时,您可以看到一个谓词的所有调用、退出、失败和重做。但是,您无法在命令行跟踪器中看到变量名称。要查看您可以执行的操作,您可以键入 h。最有趣的可能是 n 下一步和 f 完成当前目标。

或者也可以利用trace/1trace/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图形界面。

如果你想从序言中访问你的路线,你也可以写一个新的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.

您跟踪您已经访问过的图表中的哪些节点。无论如何你都需要这样做,因为你需要在你的图表中检测 cycles 以免你掉进无限递归的兔子洞。

并且在 Prolog 中,我们使用 helper 方法将状态作为 1 个或多个额外参数进行传递。一个经常使用的约定是有一个 "public" 谓词——比如说 route/3 调用一个 "private" worker 谓词,它具有相同的名称和更高的数量,比如 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!

所以你想从AB,但不仅如此,你还想知道你行程中的车站列表。

一定要仔细看下面两个相关的问题以及对问题提出的答案:

  • Definition of path/trail/walk
  • Definition of Reflexive Transitive Closure

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

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

clause(:Head, ?Body)

True if Head can be unified with a clause head and Body with the corresponding clause body. Gives alternative clauses on backtracking. For facts, Body is unified with the atom true.

所以我们可以编写一个通用谓词 expl(+Goal,-Expl)Goal 构造一个 "explanation",以防 Goal 成功:

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) 树的形式,因此输出的嵌套结构。

Edit:进一步解释上面的内容:输出是 (Goal, BodyExpl) 形式的 "explanations" 列表,其中每个 Goal 是一个被证明的(子)目标,BodyExpl 再次是用于证明 Goal 的所有递归子目标的此类解释的列表。事实上,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,因为某些变量可能仅在递归调用。