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) --> sisterOf(X,Y)Here is how the law is written in Prolog:
sisterOf(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 sisterOf(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 sisterOf (using -->e and FORALLe).
Here is the new law we wrote plus a database of primitive propositions:
===================================================
sisterOf(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:
===================================================
?- sisterOf(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(sisterOf).
?- trace(parents).
?- trace(female).
Then, when we enter the query,
sisterOf(bart, Z).
We see a printout of the clauses used by the interpreter while
it searches for a value for Z:
===================================================
[debug] 9 ?- sisterOf(bart,Z).
T Call: (7) sisterOf(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) sisterOf(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) sisterOf(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 sisterOf(bart, X)
1. FORALLX FORALLY FORALLM FORALLW
(female(Y) ^ parents(Y,M,W) ^ parents(X,M,W))
--> sisterOf(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))
--> sisterOf(bart,lisa) FORALLe (4 times)
7. sisterOf(bart,lisa) -->e 6,5
8. EXISTY sisterOf(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 sisterOf 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:
sisterOf(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# ?
This Prolog program checks and generates combinations
of quarters, dimes, nickels, and pennies that total to one
dollar:
===================================================
change([Q,D,N,P]) :-
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]),
S is 25*Q +10*D + 5*N,
S =< 100,
P is 100-S.
===================================================
Examples:
?- change([Q,D,N,P]).
lists all possible ways of giving change for a dollar.
We can also check if a proposed quantity of change totals a dollar:
?- change([2,3,4,6]).
no
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.
Notice that less-than-equals is written =< in Prolog.
=================================================== /* sorted(Xs, Ys) holds when Ys is the sorted variant of Xs */ sorted(Xs, Ys) :- permutation(Xs, Ys), ordered(Ys). /* permutation(Xs, Zs) holds true if Zs is a reordering of Xs */ permutation([], []). permutation(Xs, [Z|Zs]) :- select(Z, Xs, Ys), permutation(Ys, Zs). /* select(X, HasAnX, HasOneLessX) "extracts" X from HasAnX, giving HasOneLessX */ select(X, [X|Rest], Rest). select(X, [Y|Ys], [Y|Zs]) :- select(X, 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]). ===================================================These clauses define what it means for list Ys to be a sorted version of input list Xs. Yet, when they are read by the Prolog interpreter, e.g.,
?- sorted([5,3,7,2,9,1], Ans)the interpreter will expand the definitions of permutation and ordered, generating all possible permutations of the input list and checking to see which permutation is also ordered. The one that is ordered binds to Ans:
Ans = [1,2,3,5,7,9]The interpreter "executed" the specification, and in doing so, located 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 provide a bit of guidance to narrow the
generation of the permutations. One example of such guidance is insertion
sort, where a permutation is constructed by removing elements from
the input list and moving them to the output list, inserting them
in the appropriate position with respect to =<.
Here is how insertion sort is coded in Prolog:
===================================================
/* isort(Xs, Ys) generates Ys so that it is the sorted variant of Xs */
isort([], []).
isort([X|Xs], Ys) :- isort(Xs, Zs), insert(X, Zs, Ys).
/* insert(X, L, LwithX) inserts element X into list L, generating 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).
===================================================
Only one permutation of Xs is built --- the ordered one.
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 and must be solved by trial and error. If we add a few heuristics to narrow the search space of such trials, then the number of errors are reduced, and efficient solutions can appear.
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'].