Declarative Programming
Debugging Prolog Programs
Geraint A. Wiggins
Professor of Computational Creativity
Department of Computer Science
Vrije Universiteit Brussel
The basic Prolog debugger
I When programs go wrong we need a means of working
out why
I One way to do this is to analyse the logic of the program
(declarative debugging)
I Declarative debugging is an active research area, but we
do not yet have the technology to do it really well
I Another way is to look at the program’s procedural
behaviour
I This is less helpful, but relatively easy to do
The basic Prolog debugger (2)
I To use the debugger, we need
I Prolog, with our program loaded;
I a copy of the program, on paper or on screen;
I a clear idea of what the program is supposed to do;
I an example query, for which the program does not
exhibit the correct behaviour
I To switch on the basic debugger, use the command
?- trace.
I To switch it off again, use
?- notrace.
The basic Prolog debugger (3)
I Once the debugger is switched on, any query you ask of
Prolog will be “explained” as it is proven
I We think of each predicate in a program as having four
ways in and out, or ports. These are:
Call – where control goes in on the first attempt
to prove a goal;
Succeed – where control comes out when the goal
succeeds;
Retry – where control goes in on subsequent
attempts to prove a goal (i.e., after
backtracking)
Fail – where control comes out when the goal
fails.
I Some systems define a further port:
Exception – where control comes out on an error (e.g.,
instantiation error).
Tracing program execution
I The Prolog debugger will now stop and tell us what it is
doing, each time it passes a port. For example:
?- trace.
{The debugger will first creep —
showing everything (trace)}
true
{trace}
?- append( [a], [b], X ).
1 1 Call: append([a],[b], 89) ?
2 2 Call: append([],[b], 355) ?
2 2 Exit: append([],[b],[b]) ?
1 1 Exit: append([a],[b],[a,b]) ?
X = [a,b] ?
true
{trace}
?-
Tracing program execution (2)
{trace}
| ?- append( X, Y, [a] ).
1 1 Call: append( 69, 83,[a]) ?
1 1 Exit: append([],[a],[a]) ?
X = [],
Y = [a] ? ;
1 1 Redo: append([],[a],[a]) ?
2 2 Call: append( 365, 83,[]) ?
2 2 Exit: append([],[],[]) ?
1 1 Exit: append([a],[],[a]) ?
X = [a],
Y = [] ? ;
1 1 Redo: append([a],[],[a]) ?
2 2 Redo: append([],[],[]) ?
2 2 Fail: append( 365, 83,[]) ?
1 1 Fail: append( 69, 83,[a]) ?
false
Tracing program execution (3)
I The main components of the trace output are:
2 2 Call: append( 365, 83,[]) ?
Invocation number – an identification number which is
unique to this invocation of a predicate;
Current depth – an indication of how deep the proof
process has gone;
Port indictor – tells us which port we are at;
Current goal – tells us the current goal and its
instantiation;
Prompt – asks for what to do next.
Tracing program execution (4)
I There are several useful options to apply (mostly single
character commands):
c Creep (also carriage return) – take one proof step to the
next port;
s Skip – jump to the next exit port (Succeed or Fail) from
this invocation of this predicate;
a Abort – drop out of execution and return to the Prolog
prompt;
n Nodebug – switch off tracing and proceed as for normal
execution;
r Retry – go back to the call whose invocation number is
given
? Help – print out a list of available commands.
I Note that some commands (e.g., Skip) only make sense
at some ports (e.g., input ports), and so will not work at
others.
Spying on problem predicates
I In large programs, it is often not feasible to trace all the
way through to an error
I Often, we have a clear idea of which predicate is going
wrong, so we want to go straight to it, even in small
programs
I To do this, we use spy points
I We can set the debugger to wait until it encounters a spy
point in the program, and then it will switch on.
Spying on problem predicates (2)
I Example:
append( [], L, L ).
append( [H|T], L, [H|Z] ) :-
append( T, L, Z ).
test( X, XX ) :- append( X, X, XX ).
I Here, we can set a spy point on append/3:
?- spy append.
{The debugger will first leap —
showing spypoints (debug)}
{Spypoint placed on user:append/3}
true
{debug}
?-
Spying on problem predicates (2)
{debug}
?- test( X, [a,B,c,D,e,F] ).
+ 2 2 Call: append( 69, 69,[a, 91,c, 111,e, 131]) ?
+ 3 3 Call: append( 471,[a| 471],[ 91,c, 111,e, 131]) ?
+ 4 4 Call: append( 676,[a, 91| 676],[c, 111,e, 131]) ?
+ 5 5 Call: append( 881,[a, 91,c| 881],[ 111,e, 131]) ?
+ 5 5 Exit: append([],[a,e,c],[a,e,c]) ?
+ 4 4 Exit: append([c],[a,e,c],[c,a,e,c]) ?
+ 3 3 Exit: append([e,c],[a,e,c],[e,c,a,e,c]) ?
+ 2 2 Exit: append([a,e,c],[a,e,c],[a,e,c,a,e,c]) ?
1 1 Exit: test([a,e,c],[a,e,c,a,e,c]) ?
B = e,
D = a,
F = c,
X = [a,e,c] ?
I + means “there is a spy point here”
Spying on problem predicates (3)
I The same commands as before work in debug mode, plus
l Leap – switch off the debugger until the next spy point
is found;
+ Add spypoint to the current predicate;
– Remove spypoint from the current predicate.
I It is possible to choose which debug ports stop the
execution, using the leash/1 predicate. See the manual
for details.