CS 541 Lecture -*- Outline -*-
* Prolog Programming Techniques
Need to make copies of this or at least the examples for the students.
It won't fit on viewgraphs.
** key ideas
*** Think of programs as *relations*
plus(X,Y,Z) {<1,3,4>, <2,9,11>, ...}
reverse(L1,L2) {<1::2::3, 3::2::1>, ...}
*** state what is true
each fact and rule describes some aspect of what is true,
doesn't have to say everything (a kind of modularity)
extra facts may be inelegant, but don't affect meaning seriously.
** Recursion
*** Unary numbers
------------------------------------------
%%% The natural numbers in unary notation,
%%% and some operations
sig unary.
kind nat type.
type s nat -> nat.
type z nat.
type le nat -> nat -> o.
type lt nat -> nat -> o.
type ge nat -> nat -> o.
type gt nat -> nat -> o.
type plus nat -> nat -> nat -> o.
type diff nat -> nat -> nat -> o.
type times nat -> nat -> nat -> o.
type to_int nat -> int -> o.
type to_nat int -> nat -> o.
------------------------------------------
------------------------------------------
%%% The natural numbers in unary notation, and some operations
%%% These allow backtracking (except for to_int and to_nat).
%%% AUTHOR: Gary T. Leavens
module unary.
%%% ``le N1 N2'' succeeds if N1 is less than or equal to N2.
le z X.
le (s X) (s Y) :- le X Y.
%%% ``lt N1 N2'' succeeds if N1 is strictly less than N2.
lt z (s X).
lt (s X) (s Y) :- lt X Y.
%%% ``ge N1 N2'' succeeds if N1 is greater than or equal to N2.
ge X Y :- le Y X.
%%% ``gt N1 N2'' succeeds if N1 is strictly greater than N2.
gt X Y :- lt Y X.
%%% ``plus X Y Z'' succeeds when Z is the sum of X and Y.
plus z X X.
plus(s X) Y (s Z) :- plus X Y Z.
%%% ``diff X Y Z'' succeeds when Z is X - Y.
diff X Y Z :- plus Z Y X.
%%% ``times X Y Z'' succeeds when Z is the product of X and Y.
times z X z.
times (s X) Y Z :- (times X Y W), (plus W Y Z).
%%% ``to_int N I'' succeeds if N represents the integer I.
to_int z 0 :- !.
to_int (s M) Np1 :- (to_int M N), (Np1 is N + 1).
%%% ``to_nat I N'' succeeds if N represents the integer I.
to_nat 0 z :- !.
to_nat M (s N) :- (Mm1 is M - 1), (to_nat Mm1 N).
------------------------------------------
Q: How would you do division? exponentiation?
There are faster ways to do arithmetic in Prolog
X is Y + 1
but only works if Y has been instantiated
*** More about lists
the following comments are meant to give some idea of how
the programs are constructed.
------------------------------------------
module list_examples.
type member T -> (list T) -> o.
% X is a member of a list of the form Y::Zs either if Y = X
% or if X is a member of Zs. The cases are handled by unification.
% The failure of (member X nil) is handled by the closed world assumption.
member X (X::Xs).
member X (Y::Ys) :- member X Ys.
% an alternative translation would be...
type member2 T -> (list T) -> o.
member2 X (Y::Zs) :- X = Y.
member2 X (Y::Zs) :- not (X = Y), (member2 X Zs).
type append (list T) -> (list T) -> (list T) -> o.
% The append of lists Xs and Ys is defined by induction
% on the structure of Xs.
% basis: if Xs is nil, the result is Ys
append nil Ys Ys.
% induction: if Xs has the form X::Xs', then the result is X::Zs,
% where Zs is (append Xs' Ys Zs).
append(X::Xs) Ys (X::Zs) :- append Xs Ys Zs.
% Using append, we can do member solely using Prolog's backtracking.
% The idea is that X is a member of a list L if L can be written as
% L1 concatenated with X::nil concatenated with L2. This may seem tricky,
% but it's just based on that definition.
type member3 T -> (list T) -> o.
member3 X L :- append L1 (X::L2) L.
type reverse (list T) -> (list T) -> o.
% The reverse of a list L is defined by induction on the structure of L:
% basis: reverse of nil is nil
reverse nil nil.
% induction: if L has the form H::T, its reverse is the reverse of T
% concatenated with H::nil.
reverse (Head::Tail) L :- (reverse Tail M), (append M (Head::nil) L).
------------------------------------------
Q: Can you write subst_first of type (list T) -> T -> T -> (list T)?
Note that member2, because it uses not, which uses cut, can't be
used backwards in the same way as member:
[list_examples] ?- member2 X (1::2::3::nil).
The answer substitution:
X = 1
More solutions (y/n)? y
no (more) solutions
[list_examples] ?- member X (1::2::3::nil).
The answer substitution:
X = 1
More solutions (y/n)? y
The answer substitution:
X = 2
More solutions (y/n)? y
The answer substitution:
X = 3
More solutions (y/n)? y
no (more) solutions
[list_examples] ?- stop.
*** association lists
------------------------------------------
sig alists.
kind alist type -> type -> type.
type the_empty_alist (alist Key Val).
type acons Key -> Val -> (alist Key Val) -> (alist Key Val).
type lookup Key -> (alist Key Val) -> Val -> o.
module alists.
%%% lookup is like LISP assoc; it returns the value, if any, for a key in
%%% an alist. There is a value if the key is a member of some Key-Value pair
%%% in the alist.
lookup Key (acons Key Value Alist) Value.
lookup Key (acons K V Alist) Value :- lookup Key Alist Value.
------------------------------------------
------------------------------------------
sig alists.
kind alist type -> type -> type.
type the_empty_alist (alist Key Val).
type acons Key -> Val -> (alist Key Val) -> (alist Key Val).
type lookup Key -> (alist Key Val) -> Val -> o.
end
module alists.
%%% lookup is like LISP assoc; it returns the value, if any, for a key in
%%% an alist. There is a value if the key is a member of some Key-Value pair
%%% in the alist.
lookup Key (acons Key Value Alist) Value.
lookup Key (acons K V Alist) Value :- lookup Key Alist Value.
end
$ tjsim capitals.
...
[capitals] ?- (capitals C), (lookup peru C Capital).
The answer substitution:
Capital = lima
C = acons chile santiago (acons peru lima the_empty_alist)
------------------------------------------
*** difference lists
A difference list is an "incomplete data structure", in that it usually
contains logic variables.
------------------------------------------
sig diff_lists.
kind diff_list type -> type.
%%% difference list terms
%%% A difference list is a term of the form (diff L Z),
%%% where L is a list whose last element is the logical variable Z.
type diff (list T) -> (list T) -> (diff_list T).
%%% difference list observers.
type dl_to_list (diff_list T) -> (list T) -> o.
type list_to_dl (list T) -> (diff_list T) -> o.
type simplify (diff_list T) -> (list T) -> o.
type diffappend (diff_list T) -> (diff_list T) -> (diff_list T) -> o.
end
module diff_lists.
%%% dl_to_list and list_to_dl work in one direction only, but don't rely
%%% on the occurs check.
%%% simplify works in both directions, but relies on the occurs check.
%%% ``dl_to_list DL L'' succeeds if DL represents the list L.
dl_to_list (diff nil X) nil :- !.
dl_to_list (diff X X) nil :- !.
dl_to_list (diff (X::Y) Z) (X::W) :- dl_to_list (diff Y Z) W.
%%% ``list_to_dl L DL'' succeeds if L is represented by the diff list DL.
list_to_dl nil (diff X X).
list_to_dl (X::W) (diff (X::Y) Z) :- list_to_dl W (diff Y Z).
%%% ``simplify DL L'' succeeds if DL represents the list L.
simplify (diff X X) nil.
simplify (diff (X::Y) Z) (X::W) :- simplify (diff Y Z) W.
%%% ``diffappend DL1 DL2 DL3'' succeeds if DL3 is the concatenation
%%% of DL1 and DL2. Appending difference lists works like the arithmetic:
%%% (X-Y) + (Y-Z) = X-Z
diffappend (diff X Y) (diff Y Z) (diff X Z).
end
------------------------------------------
**** examples
$ tjsim diff_lists
...
[diff_lists] ?- dl_to_list (diff (1::2::3::X) X) L.
The answer substitution:
L = 1 :: 2 :: 3 :: nil
X = nil
More solutions (y/n)? y
no (more) solutions
[diff_lists] ?- list_to_dl (1::2::3::nil) DL.
The answer substitution:
DL = diff (1 :: 2 :: 3 :: _T1) _T1
More solutions (y/n)? y
no (more) solutions
Note that (_T1: list int) is a new variable
**** occurs check examples
[diff_lists] ?- simplify (diff (1::2::3::Y) Y) L.
The answer substitution:
L = 1 :: 2 :: 3 :: nil
Y = Y
More solutions (y/n)? n
yes
[diff_lists] ?- (diff X X) = (diff (1::2::3::Y) Y).
no (more) solutions
[diff_lists] ?- stop.
Because Prolog doesn't have the occurs check,
the last two don't work this way in Prolog.
it is easy to get yourself in trouble when Prolog tries
to unify the tail of a difference list with the logic variable
following the minus sign...
So in prolog, in response to the equivalent of the following query
produces a cyclic data structure:
?- simplify (diff (1::2::3::Y) Y) L.
Y == 1::2::3::1::2::3::1::2::3::1::2::3::1::2::3::1::2::3::...
The problem is that prolog can "unify" (diff (1::2::3::Y) Y)
with (diff X X)
because it omits the occurs check.
**** diffappend examples
But the *really* neat thing is that through the miracle of unification,
lambda Prolog can ensure that the middle term in diffappend (Y)
is the same:
[diff_lists] ?- diffappend (diff (3::X) X) (diff (4::Y) Y) Z.
The answer substitution:
Z = diff (3 :: 4 :: Y) Y
Y = Y
X = 4 :: Y
More solutions (y/n)? y
no (more) solutions
[diff_lists] ?- diffappend A (diff (1::2::3::Y) Z) R.
The answer substitution:
R = diff _T1 Z
Z = Z
Y = Y
A = diff _T1 (1 :: 2 :: 3 :: Y)
More solutions (y/n)? y
no (more) solutions
[diff_lists] ?- stop.
It's well worth thinking about how this works...