In the previous chapter, we saw how predicate-logic propositions could be reformatted with Skolem functions into clause form, where the FORALL and EXIST quantifiers are no longer written.
Prolog (``PROgramming in LOGic'') is a programming language based on the predicate calculus, reformatted in such a style. More precisely stated, Prolog is the predicate calculus restricted to FORALL, -->, ^ and a limited use of EXIST. Prolog programs are literally propositions from predicate logic, and the execution of the programs are literally proofs built using the FORALLe, -->e, ^i (and limited use of) EXISTi laws.
Here is a quick example. Recall the
classic example: ``all humans are mortal; Socrates is human;
therefore, Socrates is mortal.'' Here is how the first two
clauses are written in predicate logic:
FORALLX(human(X) --> mortal(X))
human(socrates)
The two propositions look like this when written as Prolog code:
mortal(X) :- human(X).
human(socrates).
(The FORALL is deleted, and --> is ``flipped'' into :-.)
The Prolog interpreter (theorem prover) reads and saves the above program.
We ask the interpreter to prove `` Socrates is mortal'' like this:
?- mortal(socrates).
The prover builds the proof (using the resolution technique from the
last chapter) and prints
true
meaning that it found a proof.
We can even ask the Prolog interpreter to discover who
it is that is mortal, like this:
?- mortal(Z).
(That is, we ask to prove the proposition, EXISTZ mortal(Z)).
The interpreter replies with
Z = socrates
showing that it built the proof, using socrates as the ``witness''
for Z.
Prolog was developed in the 1970s as an application of resolution theorem proving. Its ease of use, plus its inclusion of dynamic lists (like the ones in Python), make it a prominent (if not the prominent) language for solving problems in artificial intelligence, deductive databases, and data mining (let alone, theorem proving). A small amount of knowledge about Prolog goes a long way, and this chapter attempts to give you some knowledge.
The simplest form of premise is a primitive proposition, like these two:
expensive(car).
expensive(house).
Read these as asserting ``a car is expensive''; ``a house is expensive.''
We can query the the database, like this:
?- expensive(car).
true
?- expensive(coffee).
false
The first query, ?- expensive(car), is immediately proved by the
Prolog interpreter, and the second cannot be proved. (The interpreter
examined all the premises in its database, and none matches the query.)
Prolog's power comes from this form of query:
?- expensive(X)
X = car.
This query asks, ``is there something, call it X, that is expensive?''
The interpreter replies with the value for X that makes the query proved
true. More precisely stated, the query is really,
``EXISTX expensive(X) ?'' The Prolog interpreter searched and found
an individual (car) that proves the goal. (There is an implicit
use of the EXISTi-rule here.)
Prolog lets you repeat the same query, searching for multiple, different
answers, by typing a semicolon, like this:
?- expensive(X).
X = car ;
X = house ;
false.
That is, the user typed, ?- expensive(X), the interpreter replied,
X = car, the user typed ;, the interpreter replied, X = house,
the user typed ;, the interpreter replied with false (no more).
Here is a database that uses variables X and Y in its premises.
(The variables are implicitly prefixed by FORALLX and FORALLY
in the premises.)
===================================================
likes(john, icecream).
likes(john, mary).
likes(X, kim).
likes(ed, Y).
===================================================
The first premise asserts that john likes icecream. The third premise
can be read as everyone likes kim. It is more precisely understood
as FORALLX likes(X,kim). Variables (words that begin with
capital letters) in premises are implicitly universally quantified.
They are called logical variables, and they can be
assigned values while the Prolog interpreter searches for a proof
of a query.
Here are two queries (goals to be proved) for the previous database:
===================================================
?- likes(john,kim).
true
?- likes(X, icecream)
X = john ;
X = ed .
===================================================
The second query should be studied. The interpreter scanned
the premises in the database, from top to bottom, in that order.
(The order of the premises is crucial to Prolog programming.)
The first match sets logical variable X=john. The second match, made with the fourth
premise, sets X=ed and Y=icecream. The value of Y is not displayed
to the user because it is an internal match and Y does not appear
in the original query.
Sometimes an answer to a query does not matter:
?- likes(Z,kim)
true ;
Z = ed
Read the first answer, true, as saying that Z can be made equal to any value
at all
(all atoms, as they are called in Prolog. john, icecream, etc.,
are all atoms.)
This answer was found with the third clause in the database,
likes(X, kim) (``everyone/everything likes kim'').
The second answer, Z = ed, used a different clause,
namely, likes(ed, Y), to determine that ed likes kim.
FORALLX FORALLY FORALLM FORALLW: female(Y) ^ parents(Y,M,W) ^ parents(X,M,W) --> hasSister(X,Y)Here is how the law is written in Prolog:
hasSister(X,Y) :- female(Y), parents(Y,M,W), parents(X,M,W).That is, the four quantifiers, FORALL, are omitted; ^ is written as a comma; and --> is written backwards, as :-. (That is, P --> Q is written Q :- P (read ``Q if P'').)
There is a key reason why the implication is written backwards. Say that we are asked to prove hasSister(a,b) for some a and b. To do this, we must prove as subgoals all of female(b), parents(a,c,d), and parents(b,c,d), for some values c and d. So, the Prolog coding reads as a tactic for proving hasSister (using -->e and FORALLe).
Here is the new law we wrote plus a database of primitive propositions:
===================================================
hasSister(X,Y) :- female(Y), parents(Y,M,W), parents(X,M,W).
parents(bart, homer, marge).
parents(lisa, homer, marge).
parents(maggie, homer, marge).
female(marge).
female(lisa).
female(maggie).
===================================================
Here is a query:
===================================================
?- hasSister(bart, Z).
Z = lisa ;
Z = maggie ;
false.
===================================================
This shows bart has two sisters.
Here is a summary of the steps taken by the Prolog interpreter to prove Z=lisa:
Z = Y X = bart
Z = Y X = bart Y = marge
Z = Y X = bart Y = ?
Restarting the earlier step, the sixth line of the database matches
female(Y), for Y=lisa. We have these variables and their values:
Z = Y
X = bart
Y = lisa
Z = Y X = bart Y = lisa M = homer F = marge
There are no more subgoals to prove, so the original query is proved. Z=lisa prints.
Z = Y X = bart Y = ? M = ? F = ?The search sets Y=maggie, and the last two goals are reproved, with M=homer and F=marge.
You can ask the Prolog interpreter to generate an execution
trace of exactly these searching steps. The way to do so
is to trace all the predicate names in which you have
interest.
In the above example, we would type:
?- trace(hasSister).
?- trace(parents).
?- trace(female).
Then, when we enter the query,
hasSister(bart, Z).
We see a printout of the clauses used by the interpreter while
it searches for a value for Z:
===================================================
[debug] 9 ?- hasSister(bart,Z).
T Call: (7) hasSister(bart, _G464)
T Call: (8) female(_G464)
T Exit: (8) female(marge)
T Call: (8) parents(marge, _L174, _L175)
T Fail: (8) parents(marge, _L174, _L175)
T Redo: (8) female(_G464)
T Exit: (8) female(lisa)
T Call: (8) parents(lisa, _L174, _L175)
T Exit: (8) parents(lisa, homer, marge)
T Call: (8) parents(bart, homer, marge)
T Exit: (8) parents(bart, homer, marge)
T Exit: (7) hasSister(bart, lisa)
Z = lisa
===================================================
The trace shows us that the interpreter first tried to use
marge as a value for Z, bart's sister, but this failed
because the bart and marge cannot be proved to have the same
parents. Notice also that the intepreter invented its own
internal variables, _G464, _L174, and _L175, while
it did its search. These variables are not revealed to the
human user.
When we continue the trace by typing a semicolon, we see
===================================================
Z = lisa ;
T Redo: (8) female(_G464)
T Exit: (8) female(maggie)
T Call: (8) parents(maggie, _L174, _L175)
T Exit: (8) parents(maggie, homer, marge)
T Call: (8) parents(bart, homer, marge)
T Exit: (8) parents(bart, homer, marge)
T Exit: (7) hasSister(bart, maggie)
Z = maggie
===================================================
Which shows how the interpreter continues as if it failed
to find a value for Z the first time.
If we would write the proof for Y=lisa in the laws of predicate logic,
it would look like this:
===================================================
Database |- EXISTX hasSister(bart, X)
1. FORALLX FORALLY FORALLM FORALLW
(female(Y) ^ parents(Y,M,W) ^ parents(X,M,W))
--> hasSister(X,Y) premise
2. female(lisa) premise
3. parents(lisa, homer, marge) premise
4. parents(bart, homer, marge) premise
5. female(lisa) ^ parents(lisa, homer, marge)
^ parents(bart, homer, marge) ^i (2 times)
6. (female(lisa) ^ parents(lisa,homer,marge)
^ parents(bart,homer,marge))
--> hasSister(bart,lisa) FORALLe (4 times)
7. hasSister(bart,lisa) -->e 6,5
8. EXISTY hasSister(bart,Y) EXISTi 7
===================================================
The key is finding the appropriate atoms for the
variables, X, Y, M, and F. Prolog does this by exhaustive
search of the database, backtracking as needed until a
useful combination is found.
The backwards implication is called a Horn clause, and the Prolog's use of backwards implications to generate subgoals that are eventually all proved is called backwards chaining.
The previous definition of hasSister is a bit imprecise,
because it allows the Prolog interpreter to prove that every
female with parents is a sister of herself. To remove this
faulty conclusion, we can add a not-equals requirement, \=,
like this:
hasSister(X,Y) :- female(Y), parents(Y,M,W), parents(X,M,W), X \= Y.
The subgoal, X \= Y, is proved true if X and Y have distinct,
nonequal values. You can also use = to check for equality.
For example, say that we work in a library and we will program
a Prolog database of the library's holdings. Perhaps the
library stocks books and dvds, so the items can be portrayed
as data structure-values like these:
book('David Copperfield', 'Charles Dickens')
dvd('Tale of Two Cities')
The first is a book data structure that holds two data values:
the book's title and its author. (Prolog allows strings to be atoms.)
The second data structure is a dvd structure that holds the dvd's
title. The data-structure-builder names, book and dvd,
are called functors
because they look like function calls (but they are not).
If the library owns a copy of each of these items, we can write
a predicate that asserts these facts:
owns(k0, book('David Copperfield', 'Charles Dickens')).
owns(k3, dvd('Tale of Two Cities')).
The predicate, owns, lists the key (id number) and
the item.
So, the library owns item k0, the book David Copperfield
by Charles Dickens.
Here is a summary of the data structures we will use to define
the library's database:
ITEM ::= book(TITLE, AUTHOR) | dvd(TITLE)
where TITLE and AUTHOR are strings
We will use these two predicates:
PRED ::= owns(KEY, ITEM) | borrowed(KEY, PERSON, DUEDATE)
where KEY is an atom that begins with k
ITEM is defined above
PERSON is a string
DUEDATE is an int
The borrowed predicate remembers who has borrowed items from the
library. Here is a sample database:
===================================================
owns(k0, book('David Copperfield', 'Charles Dickens')).
owns(k1, book('Tale of Two Cities', 'Charles Dickens')).
owns(k2, book('Tale of Two Cities', 'Charles Dickens')).
owns(k3, dvd('Tale of Two Cities')).
owns(k4, book('Moby Dick', 'Herman Melville')).
borrowed(k2, 'Homer', 44).
borrowed(k4, 'Homer', 46).
borrowed(k3, 'Lisa', 92).
borrowed(k0, 'Lisa', 92).
===================================================
Whenever someone borrows an item, the appropriate borrowed premise
is added to the database. When someone returns an item, the borrowed
premise is removed. (Prolog has special operators, asserta and
retract, for adding and removing premises from an active database.)
Given the above database, here are some queries. First,
what has Homer borrowed?
?- borrowed(K, 'Homer', _).
K = k2 ;
K = k4 ;
false.
The database is written so that the items' keys are retrieved.
We can use them:
What books has Homer borrowed?
?- borrowed(K, 'Homer', _), owns(K, book(_,_)).
K = k2 ;
K = k4 ;
false.
The above example is important --- it shows how to retrieve
a key of a book (K) and how to use the key as well as the data-structure
name (functor)
to match only the books borrowed by Homer.
The action of matching functor names to the database is called
unification, and it gives much power to Prolog.
Note also that the _ symbol is a ``dummy variable''
that we do not care about ---
its value is not printed. (Here we do not care about the book's due date
nor the title and authors, so three dummy variables
are used in the query.)
Of course, if we wished to see the titles, we modify the query like this:
?- borrowed(K, 'Homer', _), owns(K, book(T,_)).
K = k2,
T = 'Tale of Two Cities' ;
K = k4,
T = 'Moby Dick' ;
false.
Libraries want to track overdue books. Here is a law for proving
when a borrowed item is overdue:
===================================================
isOverdue(Person, Item, Today) :- borrowed(Item, Person, DueDate), Today > DueDate.
===================================================
The words. Person, Item, and Today are logical variables, because
they begin with capital letters. Today is an int. (See our definitions
earlier.)
This law uses arithmetic on ints: it compares the int-value of Today
with the int value of DueDate, to see if today is greater than the
due date. If so, the goal, Today > DueDate succeeds (is proved).
?- isOverdue('Homer', _, 89).
true ;
true ;
false.
?- isOverdue('Homer', Item, 89).
Item = k2 ;
Item = k4 ;
false.
We query the database and learn that, if today is day 89, then Homer
has two items overdue; we can learn the keys of the items if we wish.
Perhaps a fine is calculated by how many days an item is overdue.
We can use simple arithmetic in Prolog, like this:
===================================================
hasFine(Person, Item, Today, HowMuch) :- borrowed(Item, Person, DueDate),
isOverdue(_, Item, Today),
HowMuch is Today - DueDate.
===================================================
The predicate, HowMuch is Today - DueDate is an assignment ---
a matching --- of HowMuch to the answer computed from
Today - DueDate. This ``subgoal'' succeeds when Today and
DueDate are bound to numbers so that the subtraction can be computed.
?- hasFine('Homer', I, 89, Fine).
I = k2,
Fine = 45 ;
I = k4,
Fine = 43 ;
false.
You can also do simple arithmetic with +,*,-,/.
You can compare values with >, <, =>, =<, =, \=.
Finally, it would be helpful to write a query that would return a list of all the items that Homer has borrowed. To do this, we must learn how to use lists in Prolog.
[a, b, c] [2,4,6,8] ['Homer', book('Tale of Two Cities', 'Charles Dickens')), book('Moby Dick', 'Herman Melville')) ] ['Homer', [ book('Tale of Two Cities', 'Charles Dickens')), book('Moby Dick', 'Herman Melville')) ] ] []As in Python, lists can be a mixture of values, and lists can be placed within lists. The list builder, [_,_, ..., _], is a functor, just like book(_,_) and dvd(_), seen in the previous section.
Unlike Python, Prolog does not use ints to index individual elements from a list. Instead, you can index the front (``head'') element of a list, and you can index the rest (``tail'') of the list without its front element. This head-tail indexing is done with a pattern matching that takes a little practice. The pattern looks like this: [H|T].
If we match the [H|T] pattern against [a,b,c], we have H=a, T=[b,c].
If we match the pattern against
['Homer', book('Tale of Two Cities', 'Charles Dickens')), book('Moby Dick', 'Herman Melville')) ]
we have
H = 'Homer'
T = [ book('Tale of Two Cities', 'Charles Dickens')), book('Moby Dick', 'Herman Melville')) ]
If we match the pattern against
['Homer', [ book('Tale of Two Cities', 'Charles Dickens')),
book('Moby Dick', 'Herman Melville')) ] ],
we have
H = 'Homer'
T = [[ book('Tale of Two Cities', 'Charles Dickens')),
book('Moby Dick', 'Herman Melville')) ]]
If we try to match [H|T] against [], it fails --- there is no match.
We use the [H|T] pattern to write ``functions'' that compute on lists.
Here is a function that looks at a list (the first argument) and
returns the head of the list as its answer (the second argunent):
getFirst([H|T], H) :- .
This law has no subgoals. When we use it in Prolog,
we type just this (not like above):
getFirst([H|T], H).
We use the ``function'' like this:
?- getFirst([a,b,c], Ans).
Ans = a.
?- getFirst([], Ans).
false.
The first query asks if there is a value for Ans that ``proves''
getFirst([a,b,c], Ans). The law,
getFirst([H|T], H), says we merely set the second
argument equal to the head of the first argument and the
Prolog interpreter proves
the goal with this assignment to the logical variables:
Ans=H
H=a
T=[b, c]
Here is another example:
===================================================
getRest([H|T], T).
?- getRest([a,b,c], Ans).
Ans = [b,c].
?- getRest([], Ans).
false.
===================================================
Here is a third example, which extracts the second element in
a list that has at least two elements:
===================================================
getSecond([A|[B|C]], B).
?- getSecond([a,b,c], Ans).
Ans = b.
===================================================
These small examples show that there is a style to writing
predicates in Prolog so that they behave like functions.
In C or Python, we write functions in this style:
def f(x,y) :
. . .
return ans
The parameters, x and y are listed separately from the ans.
But in
Prolog, we write,
f(x, y, ans) :- . . .
where the ans variable is written next to the parameters.
A value is ``assigned'' to ans by Prolog's matching/unification
operations that are used when the Prolog interpreter tries to solve a goal.
This style of writing a function as a predicate with parameters and answer variable grouped together is called relational programming.
We can make Prolog-style functions powerful with recursive references.
In Python, we might search a list a for an element, v, like this,
using recursive calls:
def member(v, a):
if len(a) > 0 : # is list a nonempty ?
H = a[0]
if H == v : # is v at head of a ?
return True
else :
T = a[1:]
return member(v, T) # if not, search inside tail of a
else :
return False # list a is []
We write this same recursive search in Prolog like this:
(Important: the order of the clauses is crucial to the success of the proof
search!)
===================================================
member(V, [V|_]).
member(V, [_|T]) :- member(V, T).
===================================================
We test the function with this example:
?- member(c, [a,b,c]).
Here's what happens:
The next law is tried, matching against member(V, [_|T]).
This match succeeds. The new goal is member(V,T), where
V=c
T=[b,c]
Next, a match of member(c,[b,c]) again member(V, [_|T])
against member(c,[b,c]) is attempted. This succeeds.
Since this premise was used once before, its logical variables, V and T, were used once
before, so new variants, V1 and T1, are invented. The assignments
to the logical variables look like this:
V=c
T=[b,c]
V1=V
T1=[c]
and the new goal is member(V1,T1), that is, member(c,[c]).
Remember: if a premise is used more than once to build a proof,
new instances of its logical variables are created for each successful
match. (Of course, this is because there is an implicit
use of FORALL in the premise, and we are using the premise
multiple times with the FORALLe rule!)
Finally, the goal, member(c,[c]), matches against the first premise,
and H=c. The proof is completed:
V=c
T=[b,c]
V1=V
T1=[c]
H=V1
?- member(c, [a,b,c]).
yes
If we ask the Prolog interpreter to trace the query,
?- trace(member).
we see this:
[debug] 3 ?- member(c, [a,b,c]).
T Call: (7) member(c, [a, b, c])
T Call: (8) member(c, [b, c])
T Call: (9) member(c, [c])
T Exit: (9) member(c, [c])
T Exit: (8) member(c, [b, c])
T Exit: (7) member(c, [a, b, c])
true
By the way, member is a built-in, precoded predicate in Prolog.
Try it on these examples and try to explain the answers:
===================================================
?- member(X, [1,2,3]).
X = 1 ;
X = 2 ;
X = 3 ;
false.
?- member(2, L).
L = [2|_G1043] ;
L = [_G1042, 2|_G1046] ;
L = [_G1042, _G1045, 2|_G1049] ;
L = [_G1042, _G1045, _G1048, 2|_G1052] ;
L = [_G1042, _G1045, _G1048, _G1051, 2|_G1055] ;
L = [_G1042, _G1045, _G1048, _G1051, _G1054, 2|_G1058] ;
etc.
?- member(X, L).
L = [X|_G1058] ;
L = [_G1057, X|_G1061] ;
L = [_G1057, _G1060, X|_G1064] ;
L = [_G1057, _G1060, _G1063, X|_G1067] ;
L = [_G1057, _G1060, _G1063, _G1066, X|_G1070] ;
L = [_G1057, _G1060, _G1063, _G1066, _G1069, X|_G1073] ;
etc.
===================================================
In a similar way, we can write a Prolog function to see if
a value is not a member of a list: (Here, \= means !=.)
===================================================
notMember(V, []).
notMember(V, [H|T]) :- V \= H, notmember(V, T).
===================================================
This two-line function computes the same steps as this Python function:
def notMember(v, a):
if a == [] :
return True
else :
if v != a[0] :
return notMember(v, a[1:]) #search tail of a
else :
return False # v == a[0], so stop
Here is a third example, where we write a function that accepts
a list of ints and constructs a list that has all the ints doubled
in value. It will behave like this:
?- double([1,2,3], Y).
Y = [2,4,6].
?- double([], Y).
Y = [].
Here is the function:
===================================================
double([], []).
double([H|T], [HH|TT]) :- HH is 2 * H, double(T, TT).
===================================================
The computation made by double looks like this in Python:
def double(a) :
if a == [] :
return []
else :
HH = a[0]
TT = double(a[1:])
return [HH] + TT
Here is a fourth example, which totals the ints in a list:
===================================================
total([], 0).
total([N|Rest], Answer) :- total(Rest, Subanswer),
Answer is N + Subanswer.
?- total([1,2,3,4], T).
T = 10.
===================================================
We should finish the library-database example from the
previous section. How do we compute a list holding
all the items borrowed by a user? We can do it with
a predicate that uses
lists, patterns, and recursion.
But
this issue appears so often in practice that
Prolog has a built-in predicate, findall, that does the job for us.
Here is how to define the list of books borrowed by 'Homer':
?- findall(Item, borrowed(Item, 'Homer', _), AnswerList).
AnswerList = [k2, k4].
The predicate, findall(WHAT, PREDICATE, ANSWERLIST),
repeatedly proves PREDICATE, collecting each value
of variable WHAT appearing in PREDICATE, saving the values
in a new list named ANSWERLIST.
In the above example, borrowed(Item, 'Homer', _) is executed,
and Item = k2. Then, borrowed(Item, 'Homer',_) is executed
again, and Item = k3. Then, borrowed(Item, 'Homer',_) is
executed again, and there is failure (false). The two answers
are collected into the list, AnswerList = [k2, k4].
We use our new knowledge to define this useful predicate:
===================================================
borrowedItems(Person, List) :- findall(Item, borrowed(Item, Person, _), List).
===================================================
so that borrowedItems('Homer', L) generates the list, L = [k2, k4], of items
Homer borrowed.
Note that
borrowedItems('Marge', L) generates L = [] because
Marge has nothing borrowed.
Similarly, we see that
?- findall(Item, borrowed(Item, _,_), AnswerList).
AnswerList = [k2, k4, K3, k0]
lists all items borrowed by all persons.
and
?- findall(DueAt, borrowed(K, _, DueAt), DateList).
DateList = [44, 46, 92, 92].
lists the dates all borrowed items are due.
Notice that the value(s) of K are not displayed.
But
you can use findall to save multiple values, say the
Item and DueDate of each book borrowed:
?- findall([Item,DueDate], borrowed(Item, 'Homer', DueDate), Ans).
Ans = [[k2, 44], [k4, 46]].
If you understood this example, then you can do these exercises:
By the way,
many functional and scripting languages, e.g., Python, have their
own versions of findall that operate on list arguments --- it is called a list comprehension
operator. Here is how you do it in Python:
ANSWERLIST = [ OP(X) for X in ARGUMENTLIST if PREDICATE ]
Here is some Python code:
nums = [0,1,2,3,4,5,6,7,8,9] # you can also say: nums = range(10)
# we collect the even-valued ints in nums and triple them:
answer = [ 3*n for n in nums if n % 2 == 0 ]
print answer # prints [0,6,12,18,24]
How do code this in C? In Java or C# ?
Here is a first example.
This Prolog program checks and generates change adding up to a dollar
consisting of quarters, dimes, nickels, and pennies.
===================================================
change(Q,D,N,P) :-
/* the following three lines define the data domains for
variables Q, D, and N: */
member(Q,[0,1,2,3,4]), /* quarters */
member(D,[0,1,2,3,4,5,6,7,8,9,10]) , /* dimes */
member(N,[0,1,2,3,4,5,6,7,8,9,10, /* nickels */
11,12,13,14,15,16,17,18,19,20]),
Sum is 25*Q +10*D + 5*N,
Sum =< 100,
P is 100-Sum.
===================================================
Examples:
?- change(Q,D,N,P).
lists all possible ways of giving change for a dollar.
(More precisely,
?- findall((Q,D,N,P), change(Q,D,N,P), AllAnswers).)
?- change(0,D,N,P).
lists all the ways of generating change that excludes quarters.
We can also check if a proposed quantity of change totals a dollar:
?- change(2,3,4,6).
false
since 2 quarters, 3 dimes, 4 nickels, and 6 pennies do not make a dollar,
and
?- change(2,3,2,P).
P=10
calculates how many pennies are needed to make all the coins total
a dollar.
The program defines what it means when a quantity of coins totals a dollar --- the program lists the constraints on the possible solutions to the puzzle. But the program does not give an algorithm for counting out the coins. The Prolog interpreter supplies the search (control) strategy for finding coins that total a dollar. In this sense, the Prolog interpreter "executes the specification" or "synthesizes an algorithm that implements the specification."
As a Prolog programmer, your job is to write definitions that constrain the possible solutions and let the Prolog interpreter do the rest.
We can make the previous program more general and more practical by making it simpler:
===================================================
/* changeFor(HowMuch, Q,N,D,P) holds true if the sum
of the monetary values of the Q quarters,
D dimes, N nickels, and P pennies totals HowMuch.
*/
changeFor(HowMuch, Q,D,N,P) :-
/* we list the atoms in order of preference for use: */
member(Q,[5,4,3,2,1,0]), /* quarters */
member(D,[5,4,3,2,1,0]), /* dimes */
member(N,[4,3,2,1,0]), /* nickels */
member(P,[4,3,2,1,0]), /* pennies */
HowMuch is 25*Q +10*D + 5*N + P.
===================================================
Each member predicate constrains the domain ("data type") of a logical variable. The order in which we list the domain values states our preference as to which values should be tried first.
Since most people prefer large coins for their change (and not lots of pennies!), we list the elements from largest to smallest.
This makes the first solution that the Prolog interpreter produces the one that is most desired.
Try this example, and as an exercise, write a program in an assignment language that does the same as the above, that is, generates all ways of making change, where the answers are ordered from most desirable to least. (Good luck!)
Here is a second, famous example.
We say that an input list (or an array) is sorted into a new list
if the new list is a permutation (reordering) of the input list
and is ordered (its elements are arranged according to a comparison
operator, <=).
Just stating this requirement becomes a solution to the problem in
Prolog:
===================================================
/* select(X, HasAnX, HasOneLessX) "extracts" X from HasAnX,
giving HasOneLessX. (That is, [X|HasOneLessX] is a permutation
of HasAnX.) The definition is built into Prolog, but here it is, anyway:
*/
select(X, [X|Rest], Rest).
select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).
/* permutation(Xs, Zs) holds true if Zs is a reordering of Xs */
permutation([], []).
permutation(Xs, [Z|Zs]) :- select(Z, Xs, Ys), permutation(Ys, Zs).
/* ordered(Xs) holds true if the elements in Xs are ordered by < */
ordered([]).
ordered([X]).
ordered([X,Y|Rest]) :- X =< Y, ordered([Y|Rest]).
/* sorted(Xs, Ys) holds when Ys is the sorted variant of Xs */
sorted(Xs, Ys) :- permutation(Xs, Ys), ordered(Ys).
===================================================
These clauses define what it means for list Ys to be a sorted version
of input list Xs. Yet, when the clauses are read by the Prolog interpreter,
e.g.,
?- sorted([5,3,7,2,9,1], Ans)
the interpreter expands the definitions of permutation and
ordered, generates all possible permutations of the
input list and checks to see which permutation is also ordered.
The one that is ordered binds to Ans:
Ans = [1,2,3,5,7,9]
The interpreter "executes" the specification, and in doing so,
discovers the answer!
This worked because the Prolog interpreter added its own control structure to the definitional clauses. In this sense, "specification + control = algorithm" is the slogan behind Prolog --- the human provides a specification of the problem, the interpreter provides control structure, and the result is an algorithm that computes the solution.
Alas, the algorithm synthesized here is slow and naive --- it grinds through all permutations of the input list to find the very one that is also ordered. A human can refine the specification so that it further constrains the range of candidate solutions:
=================================================== /* selectLeast(X, Ys, Zs) if X is the least element in list Ys and [X|Zs] is a permutation of Ys, that is, Zs is Ys with X removed. */ selectLeast(X, [X], []). selectLeast(X, [Y|Ys], [Y|Zs]) :- selectLeast(X, Ys, Zs), X < Y. selectLeast(Y, [Y|Ys], [X|Zs]) :- selectLeast(X, Ys, Zs), Y =< X. /* permutation is the same as before, as are ordered and sorted: (but note use of selectLeast) */ permutationL([], []). permutationL(Xs, [Z|Zs]) :- selectLeast(Z, Xs, Ys), permutationL(Ys, Zs). sortedL(Xs, Ys) :- permutationL(Xs, Ys), ordered(Ys). ===================================================If you run this program and trace(permutationL), you will see that the very first permutation that it generates is ordered. The refined selectLeast constrained the range of possible solutions, in this case, to only one.
=================================================== /* insert(X, L, LwithX) inserts element X into ordered list L, generating ordered list, LwithX */ insert(X, [], [X]). insert(X, [Y|Ys], [X, Y | Ys]) :- X =< Y. insert(X, [Y|Ys], [Y|Zs]) :- X > Y, insert(X, Ys, Zs). /* permutationI(Xs, Ys) uses insert to generate list Ys as a permutation of list Xs: */ permutationI([], []). permutationI([X|Xs], Ys) :- permutationI(Xs, Zs), insert(X, Zs, Ys). sortedI(Xs, Ys) :- permutationI(Xs, Ys), ordered(Ys). ===================================================
We might say that programming is the activity of refining (constraining) specifications so that they become efficient solutions of problems. Prolog is the "workbench" you use to do this.
Prolog works great for game playing, where multiple searches must be made to calculate the consequences of all next possible moves. (Both sorting and change-making are "games" where the "player" must choose how to order elements or add coins.) Prolog is also great for solving problems that have no best strategies, as in many data mining and "big data" situations. If we refine key definitions to narrow the search space, then Prolog becomes an ideal database query language. With conscious, repeated refinements, we use Prolog for classical algorithmic problem solving.
In addition to the commands shown in the previous examples, here are a few more.
?- listing.to see a list of the clauses in the database.
double([], []) :- write('empty list'), nl. double([H|T], [HH|TT]) :- write('nonempty list. H='), write(H), write(' T='), write(T), nl, HH is 2 * H, write('HH='), write(HH), nl, double(T, TT).and this query,
?- double([1,2,3], Z).we see this printout:
nonempty list. H=1 T=[2, 3] HH=2 nonempty list. H=2 T=[3] HH=4 nonempty list. H=3 T=[] HH=6 empty list Z = [2, 4, 6].
?- halt.to stop the interpreter.
:- ['file1.pl', 'file2.pl', 'file3.pl'].