07.363 Logic Programming: Lecture 4
Prolog programs
Program is collection of predicates.
Predicate has one or more clauses.
Clause is either a trivial fact
append( [], L, L )
Or of the form Head:-Goal.
append( [H|T], L, [H|TL] ) :-
append( T, L, TL ).
What's in a Goal?
A Goal is (like everything else) a Prolog term.
The comma (,) and semicolon (;) have special meanings:
G1, G2
call G1. If it succeeds, call G2. If G1
fails, then just fail
G1 ; G2
call G1. If that succeeds, then carry on.
If something later fails (or G1 fails), then call G2 and carry
on.
You can avoid semicolon altogether by writing another clause
(although sometimes another predicate is needed).
Selecting the right clause
Note: A predicate name includes the number of arguments, and is often
written name/N; e.g. append/3. If you miss an argument
off, then you just defined a different predicate! Beware!
Prolog will not tell you!
What should happen if Prolog meets a predicate that has more than one
matching clause?
Select the first matching clause and ignore the rest?
Select the first matching clause, but remember the rest in case
the first leads to failure
Select all the clauses, and try them in (simulated) parallel
Which is best?
The first alternative is used in ``committed choice'' languages. It
has the merit of efficiency, but can only ever return one solution.
The second alternative is used in Prolog. It is still efficient
(``choice points'' can be kept on a stack), and allows multiple
solutions to be returned one-at-a-time,
The third alternative is more costly, but allows multiple solutions to
be returned together.
Backtracking
When Prolog encounters a predicate with more than one clause, it looks
for the first clause that matches the current goal, and makes a quick
check for any other clauses that could match (usually just looking at
the first argument).
If there is another possible match, a ``choice point'' is pushed on a
control stack.
Later, when Prolog encounters a predicate that fails, the top choice
point is popped off the control stack, and execution resumes at that
clause.
Any variable bindings made after the last choice point are undone.
Example
append( [], L, L ).
append( [H|T], L, [H|TL] ) :-
append( T, L, TL ).
Call append(A, B, [1,2]). This can match either clause.
Select the first, and remember the second.
Succeeds, with bindings A=[] and B=[1,2].
Now, if Prolog is asked to backtrack (perhaps the complete goal was
append(A,B,[1,2]), B = [_]).
The latest choice point is in append. Undo the bindings for
A and B and select the second clause.
This creates new bindings A=[_X] and B=L, and sets up a
new subgoal, append(_X, L, [2]).
Functions in Prolog
In Gofer,
append [] l = l
append (h:t) l = h:tl
where tl = append t l
In Prolog,
append( [], L, L ).
append( [H|T], L, TL ) :-
append( T, L, TL ).
What do you get? Inverse for free!
| ?- append( [1,2], [3,4], L ).
L = [1,2,3,4]
| ?- append( A, B, [1,2,3,4] ).
A = [], B = [1,2,3,4] ;
A = [1], B = [2,3,4] ;
A = [1,2], B = [3,4] ;
A = [1,2,3], B = [4] ;
A = [1,2,3,4], B = []
Simplifying boolean functions
member x [] = False
member x (h:t)
| x == t = True
| otherwise = member x t
Straight translation
member( X, [], false ).
member( X, [X|_], true ).
member( X, [_|L], Mem ) :-
member( X, L, Mem ).
We can call with member(1. [1,2], true) and use failure to mean
false. This means the first clause can never match, and the predicate
simplifies to
member( X, [X|_] ).
member( X, [_|T] ) :-
member( X, T ).
Programming for relations
Sometimes functions coded as predicates are reversable.
Sometimes they are not.
append/3 is reversable.
member/2 is reversable.
How about reverse?
reverse l = reverse' l []
where
reverse' [] l = l
reverse' (h:t) l = rev
where rev = reverse' t (h:l)
Reverse in Prolog
rev( L, Rev ) :- rev( L, [], Rev ).
rev( [], Rev, Rev ).
rev( [H|T], SoFar, Rev ) :-
rev( T, [H|SoFar], Rev ).
What happens to
| ?- rev( L, [1,2,3] ).
L = [3,2,1] ;
...hangs...
What went wrong?
Trace rev(L, [1,2,3]):
(1) 0 Call: rev(L,[1,2,3]) ?
(2) 1 Call: rev(L,[],[1,2,3]) ?
(2) 2 Head [2]: rev(L,[],[1,2,3]) ?
(3) 2 Call: rev(_,[_],[1,2,3]) ?
(3) 3 Head [2]: rev(_,[_],[1,2,3]) ?
(4) 3 Call: rev(_,[_,_],[1,2,3]) ?
(4) 4 Head [2]: rev(_,[_,_],[1,2,3]) ?
(5) 4 Call: rev(_,[_,_,_],[1,2,3]) ?
(5) 4 Exit: rev([],[1,2,3],[1,2,3]) ?
(4) 3 Exit: rev([1],[2,3],[1,2,3]) ?
(3) 2 Exit: rev([2,1],[3],[1,2,3]) ?
(2) 1 Exit: rev([3,2,1],[],[1,2,3]) ?
(1) 0 Exit: rev([3,2,1],[1,2,3]) ?
L = [3,2,1] ;
Trace, continued
(5) 5 Head [2]: rev(_,[_,_,_],[1,2,3]) ?
(6) 5 Call: rev(_,[_,_,_,_],[1,2,3]) ?
(6) 6 Head [2]: rev(_,[_,_,_,_],[1,2,3]) ?
(7) 6 Call: rev(_,[_,_,_,_,_],[1,2,3]) ?
etc.
Prolog (ever keen) is searching progressively longer lists for
another possible match.
Taming rev
How can rev be tamed? Need to stop recursion if the reversed
list is longer than the original. Add extra argument
reverse( L, Rev ) :-
reverse( L, [], Rev, Rev ).
reverse( [], Rev, [], Rev ).
reverse( [H|T], SoFar, [_|Bound] Rev ) :-
reverse( T, [H|SoFar], Bound, Rev ).
Now, the clauses for reverse/4 match each element in L
with a corresponding element in Rev, so the recursion cannot
get away (unless both L and Rev are unbound, but then
that's what you want!)
Moral
Inverses do not always come for free.
Taming search is a key skill in logic programming.
You must always ask what arguments need to be bound, and what happens
if a predicate is backtracked into.
Some inverses are not well defined. E.g. the Quintus Prolog library
predicate append/2 takes a list of lists, and appends them all
together. Thus, append([ [1], [2]], L) will bind L to
[1,2]. This predicate cannot be expected to work the other way
round, since the list of lists can contain infinitely many empty lists.