During debugging, the debugger prints out a sequence of goals in various states of instantiation in order to show the state the program has reached in its execution. However, in order to understand what is occurring it is necessary to understand when and why the debugger prints out goals. As in other programming languages, key points of interest are predicate entry and return, but in Prolog there is the additional complexity of backtracking. One of the major confusions that novice Prolog programmers have to face is the question of what actually happens when a goal fails and the system suddenly starts backtracking. The Procedure Box model of Prolog execution views program control flow in terms of movement about the program text. This model provides a basis for the debugging mechanism in development systems, and enables the user to view the behavior of the program in a consistent way.
Let us look at an example Prolog predicate :
*--------------------------------------* Call | | Exit ---------> + descendant(X,Y) :- offspring(X,Y). + ---------> | | | descendant(X,Z) :- | <--------- + offspring(X,Y), descendant(Y,Z). + <--------- Fail | | Redo *-------------------+------------------* | <------------------------------+ Exception
The first clause states that Y is a descendant of X if Y is an offspring of X, and the second clause states that Z is a descendant of X if Y is an offspring of X and if Z is a descendant of Y. In the diagram a box has been drawn around the whole predicate and labeled arrows indicate the control flow in and out of this box. There are five such arrows, which we shall look at in turn.
descendant(X,Y)
is required to be
satisfied, control passes through the Call port of the
descendant box with the intention of matching a component clause
and then satisfying the subgoals in the body of that clause.
Note that this is independent of whether such a match is possible;
i.e. first the box is called, and then the attempt to match takes
place. Textually we can imagine moving to the code for descendant when
meeting a call to descendant in some other part of the code.
throw/1
or
raise_exception/1
or by an error in a built-in predicate.
See Exception. Control now passes out of the Exception
port of the descendant box and the system continues to pass the
exception to outer levels. Textually we move back to the code that
called this predicate and keep moving backwards up the code
looking for a call to catch/3
or on_exception/3
.
In terms of this model, the information we get about the procedure box is only the control flow through these five ports. This means that at this level we are not concerned with which clause matches, and how any subgoals are satisfied, but rather we only wish to know the initial goal and the final outcome. However, it can be seen that whenever we are trying to satisfy subgoals, what we are actually doing is passing through the ports of their respective boxes. If we were to follow this, then we would have complete information about the control flow inside the procedure box.
Note that the box we have drawn round the predicate should really be seen as an invocation box. That is, there will be a different box for each different invocation of the predicate. Obviously, with something like a recursive predicate, there will be many different Calls and Exits in the control flow, but these will be for different invocations. Since this might get confusing each invocation box is given a unique integer identifier.
In addition to the five basic ports discussed above, there are two more ports for invocations involving a blocked goal: