If you neither want to use debugging, nor write additional methods using helper predicates, a third option is to make use of the many built-in capabilities of SWI-Prolog for meta-programming. In this case, the clause/2
predicate might be helpful (it is part of the ISO standard, so other Prolog dialects might have it too, but I haven't checked):
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
.
So we could write a generic predicate expl(+Goal,-Expl)
that constructs an "explanation" for a Goal
, in case the Goal
succeeds:
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]) :- !.
This yields:
?- expl([route(sea,jfk)],X).
X = [(route(sea, jfk), [(flight(sea, msp), []), (flight(msp, jfk), [])])].
It also supports backtracking and queries with variables:
?- 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.
Note that such an explanation is not necessarily a list, but generally takes the form of a (SLD) tree, hence the nested structure of the output.
Edit: To explain the above a bit further: The output is a list of "explanations" of the form (Goal, BodyExpl)
, where each Goal
is a (sub)goal that was proved, and BodyExpl
is again a list of such explanations for all the recursive subgoals that were used for proving Goal
. For facts, the BodyExpl
part is simply empty. In general, this structure can be nested arbitrarily deep (depending on your input program).
If you find this hard to read, are not interested in processing the output any further, and only want a human-readable explanation, you could do the following:
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]).
This yields for example:
?- 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).
Note that you want to call write_expl
only after making the recursive calls to expl
, since some of the variables may only get instantiated during the recursive calls.