Consider the following predicate which calculates the factorial of a number:
fac(0, 1). fac(N, X) :- N1 is N - 1, fac(N1, Y), X is N * Y.
The factorial of 5 can be found by typing:
| ?- fac(5, X). X = 120
However, backtracking into the above predicate by typing a semicolon at
this point, causes an infinite loop because the system starts attempting
to satisfy the goals fac(-1, X).
, fac(-2, X).
, etc. The
problem is that there are two clauses that match the goal fac(0,
F).
, but the effect of the second clause on backtracking has not been
taken into account. There are at least three possible ways of fixing
this:
fac(0,1) :- !.
Adding the cut essentially makes the first solution the only one for the
factorial of 0 and hence solves the immediate problem. This solution is
space-efficient because as soon as Prolog encounters the cut, it knows
that the predicate is determinate. Thus, when it tries the second
clause, it can throw away the information it would otherwise need in
order to backtrack to this point. Unfortunately, if this solution is
implemented, typing fac(-1, X)
still generates an infinite
search.
fac(N, X) :- N > 0, N1 is N - 1, fac(N1, Y), X is N * Y.
This also solves the problem, but it is a more robust solution because this way it is impossible to get into an infinite loop.
This solution makes the predicate logically determinate--there
is only one possible clause for any input--but the Prolog system is
unable to detect this and must waste space for backtracking information.
The space-efficiency point is more important than it may at first seem;
if fac/2
is called from another determinate predicate, and if the
cut is omitted, Prolog cannot detect the fact that fac/2
is
determinate. Therefore, it will not be able to detect the fact that the
calling predicate is determinate, and space will be wasted for the
calling predicate as well as for fac/2
itself. This argument
applies again if the calling predicate is itself called by a determinate
predicate, and so on, so that the cost of an omitted cut can be very
high in certain circumstances.
fac(N, X) :- ( N > 0 -> N1 is N - 1, fac(N1, Y), X is N * Y ; N =:= 0 -> X = 1 ).
This solution is as robust as solution 2, and more efficient than solution 1, since it exploits conditionals with arithmetic tests (see Conditionals and Disjunction for more information on optimization using conditionals).