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.