如何让 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/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
图形界面。
如果你想从序言中访问你的路线,你也可以写一个新的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!
所以你想从A
到B
,但不仅如此,你还想知道你行程中的车站列表。
一定要仔细看下面两个相关的问题以及对问题提出的答案:
- 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
,因为某些变量可能仅在递归调用。
我有以下事实和规则:
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/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
图形界面。
如果你想从序言中访问你的路线,你也可以写一个新的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!
所以你想从A
到B
,但不仅如此,你还想知道你行程中的车站列表。
一定要仔细看下面两个相关的问题以及对问题提出的答案:
- 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 andBody
with the corresponding clause body. Gives alternative clauses on backtracking. For facts,Body
is unified with the atomtrue
.
所以我们可以编写一个通用谓词 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
,因为某些变量可能仅在递归调用。