Prolog Notes-Complete
Prolog Notes-Complete
3 Further Programming
4.3.1 Programming Paradigms
1
Introduction to Prolog(Programming in Logic)
Prolog is a logic language that is particularly suited to programs that involve symbolic or non-
numeric computation. For this reason it is a frequently used language in Artificial Intelligence
where manipulation of symbols and inference about them is a common task.
Prolog consists of a series of rules and facts. A program is run by presenting some query and
seeing if this can be proved against these known rules and facts.
1. Simple Facts
In Prolog we can make some statements by using facts. Facts either consist of a particular item
or a relation between items. For example we can represent the fact that it is sunny by writing
the program:
sunny.
?- is the Prolog prompt. To this query, Prolog will answer yes. sunny is true because (from
above) Prolog matches it in its database of facts.
Facts have some simple rules of syntax. Facts should always begin with a lowercase letter and
end with a full stop. The facts themselves can consist of any letter or number combination, as
well as the underscore _ character. However, names containing the characters -,+,*,/, or other
mathematical operators should be avoided.
raining. /* it is raining */
2
These describe a particular set of circumstances for some character john. We can interrogate
this database of facts, by again posing a query. For example: {note the responses of the Prolog
interpreter are shown in italics}
?- john_Forgot_His_Raincoat.
yes
?- raining.
yes
?- foggy.
no
The first two queries succeed since they can be matched against facts in the database above.
However, foggy fails (since it cannot be matches) and Prolog answers no since we have not told
it this fact.
The above fact says that a relationship likes links john and mary. This fact may be read as either
john likes mary or mary likes john. This reversibility can be very useful to the programmer,
however it can also lead to some pitfalls. Thus you should always be clear and consistent how
you intend to interpret the relation and when.
Finally, remember names of the relations are defined by you. With the exception of a few
relations that the system has built into it, the system only knows about relations that you tell it
about.
3
Facts with Arguments Examples 1
An example database. It details who eats what in some world model.
age(agnes,41). /* Agnes is 41 */
age(george,72). /* George is 72 */
age(ian,2). /* Ian is 2 */
age(thomas,25). /* Thomas is 25 */
4
If we now ask some queries we would get the following interaction:
no /* No. two and 2 are not the same and therefore don't match */
The predicate named in the goal and databases are the same.
Both predicates have the same arity.
All of the arguments are the same.
How do we say something like "What does Fred eat"? Suppose we had the following fact in our
database:
eats(fred,mangoes).
However Prolog will say no. The reason for this is that what does not match with mangoes. In
order to match arguments in this way we must use a Variable. The process of matching items
with variables is known as unification. Variables are distinguished by starting with a capital
letter. Here are some examples:
X /* a capital letter */
5
Thus returning to our first question we can find out what fred eats by typing
?- eats(fred,What).
What=mangoes
As a result of this query, the variable What has matched (or unified) with mangoes. We say that
the variable What now has the binding mangoes. When we pose a query, if the query is
successful, Prolog prints both the variable and the variable name, as we see above.
Variable Examples 1
Let's consider some examples using facts. First consider the following database.
loves(john,mary).
loves(fred,hobbies).
Who = hobbies /* Note the to Prolog Who is just the name of a variable, it */
yes /* semantic connotations are not picked up, hence Who unifies */
/* with hobbies */
Variable Examples 2
Here are some more difficult object/variable comparisons. Consider the following database
showing a library of cassette tapes. Notice the final argument to tape, which is the name of a
favourite song from the album.
tape(1,van_morrison,astral_weeks,madam_george).
tape(2,beatles,sgt_pepper,a_day_in_the_life).
6
tape(3,beatles,abbey_road,something).
tape(4,rolling_stones,sticky_fingers,brown_sugar).
tape(5,eagles,hotel_california,new_kid_in_town).
Artist=eagles
Album=hotel_california
Fave_Song=new_kid_in_town
yes
yes
4. Rules
So far we have looked at how to represent facts and to query them. Now we move on to rules.
Rules allow us to make conditional statements about our world. Each rule can have several
variations, called clauses. These clauses give us different choices about how to perform
inference about our world. Let's take an example to make things clearer.
Rule 1
human(X).
The clause can be read in two ways (called either a declarative or a procedural interpretation).
The declarative interpretation is "For a given X, X is mortal if X is human." The procedural
interpretation is "To prove the main goal that X is mortal, prove the subgoal that X is human."
7
8
Rule 2
To continue our previous example, lets us define the fact 'Socrates is human' so that our
program now looks as follows:
mortal(X) :-
human(X).
human(socrates).
?- mortal(socrates).
yes
Why was this? Well in order to solve the query ?- mortal(socrates)., we used the rule we saw
previously. This said that in order to prove someone mortal, we had to prove them to be human.
Thus from the goal Prolog generates the subgoal of showing human(socrates).
In the above example we were able to match human(socrates) against the database described at
the top of this card. In Prolog we say that the subgoal succeeded, and as a result the overall goal
succeeded. We know when this happens because Prolog prints yes.
Rule 3
We can also use variables within queries. For example, we might wish to see if there is
somebody who is mortal. This is done by the following line.
?- mortal(P).
yes
This means that Prolog was able to prove the goal by binding the variable P to socrates. This
was done by again proving someone was mortal by proving the subgoal that they were human.
Prolog thus asked if there was any P that was human. This matches against the clause
human(socrates) thereby binding P to socrates. This binding is then passed back to the parent
goal, and the results in the printout we saw above.
9
Rule 4
Sometimes we may wish to specify alternative ways of proving a particular thing. This we can
do by using different rules and facts with the same name. For example, we can represent the
sentence 'Something is fun if its a red car or a blue bike or it is ice cream' as follows:
fun(X) :- /* an item is fun if */
This program says that we have three ways of finding out if something is fun. Something is fun if
it is a red and a car or blue and a bike, or if it is ice cream. These three options are represent in
Prolog by three clauses of the predicate fun. Just like we saw for pure facts, Prolog will start
from the first clause (beit a rule or fact) of fun and try that. If that does not succeed, we try the
next clause. We only fail when we run out of rules or facts to try.
Rule 5
All identically-named variables within a particular rule (e.g. all occurrences of, say, X in the first
fun rule below) are constrained to have one and the same instantiation for each solution to a
particular query. Identical variable names in separate rules are totally independent, just as if
different variable names had been used. Because the consequences of the preceding two
sentences are so fundamental to Prolog (and so often misunderstood), we recommend that you
read them again. To give an example
The program
fun(X) :-
red(X),
car(X).
fun(X) :-
blue(X),
bike(X).
Looks to Prolog Like
fun(X_1) :-
red(X_1),
car(X_1).
fun(X_2) :-
10
blue(X_2),
bike(X_2).
Thus variable name scoping is per-individual rule (often called a clause). The same variable
name may appear in different clauses of a rule, or rules with different names. Each time it is
treated as something specific to its context. A variable X may occur in the program many times
with many different bindings. It will only have the same bindings when you tell it to.
Examples of Rules 1
Consider the following program:
fun(X) :-
red(X),
car(X).
fun(X) :-
blue(X),
bike(X).
car(vw_beatle).
car(ford_escort).
bike(harley_davidson).
red(vw_beatle).
red(ford_escort).
blue(harley_davidson).
Above is both our previous program for finding fun items and facts describing some red and blue
items and some cars and bikes. Let's now use the above program and see if a harley_davidson is
fun. To do this we can ask Prolog the following question.
?- fun(harley_davidson). /* to which Prolog will reply */
To execute this query, Prolog will first see if harley_davidson is red, however only vw_beatles
and ford_escorts are defined as being red. Hence the query red(harley_davidson) will fail. This
means that the first clause of fun will fail. As a result, we now try the second clause of fun. This
will mean that we will attempt the subgoals blue(harley_davidson) and bike(harley_davidson).
Both these goals match facts in the database. As a result fun succeeds as we saw above.
11
Examples of Rules 2
fun(X) :-
red(X),
car(X).
fun(X) :-
blue(X),
bike(X).
car(vw_beatle).
car(ford_escort).
bike(harley_davidson).
red(vw_beatle).
red(ford_escort).
blue(harley_davidson).
We can also ask our program to find fun items for us. To do this we can pose the following
question.
?- fun(What).
yes
Let's see how Prolog deals with this query. Firstly we will try the first clause of fun. This results
in us trying the goal red(What). This succeeds matching the first clause of red with the binding
What=vw_beatle. Now we attempt the goal car(vw_beatle). This matches the first clause of car,
and, as a result, the fun goal succeeds.
12
5. Query/Goal
Introducing Backtracking
eats(fred,pears).
eats(fred,t_bone_steak).
eats(fred,apples).
So far we have only been able to ask if fred eats specific things. Suppose that I wish to instead answer the question,
"What are all the things that fred eats". To answer this I can use variables again. Thus I can type in the query
?- eats(fred,FoodItem).
FoodItem = pears
This is because it has found the first clause in the database. At this point Prolog allows us to ask if there are other
possible solutions. When we do so we get the following.
FoodItem = t_bone_steak
FoodItem = apples
If we ask for further solutions, Prolog will answer no, since there are only three ways to prove fred eats
something. The mechanism for finding multiple solution is called backtracking. This is an essential
mechanism in Prolog and we shall see more of it later.
Backtracking in Rules
We can also have backtracking in rules. For example consider the following program.
hold_party(X):-
birthday(X),
happy(X).
birthday(tom).
birthday(fred).
birthday(helen).
happy(mary).
happy(jane).
happy(helen).
13
If we now pose the query
?- hold_party(Who).
In order to solve the above, Prolog first attempts to find a clause of birthday, it being the first subgoal of birthday.
This binds X to tom. We then attempt the goal happy(tom). This will fail, since it doesn't match the above database.
As a result, Prolog backtracks. This means that Prolog goes back to its last choice point and sees if there is an
alternative solution. In this case, this means going back and attempting to find another clause of birthday. This time
we can use clause two, binding X to fred. This then causes us to try the goal happy(fred). Again this will fail to match
our database. As a result, we backtrack again. This time we find clause three of birthday, and bind X to helen, and
attempt the goal happy(helen). This goal matches against clause 3 of our happy database. As a result, hold_party will
succeed with X=helen.
Query Example 1
Consider the following examples.
red(X), /* red */
blue(X), /* blue */
red(apple_1).
red(block_1).
red(car_27).
/* database of cars */
car(desoto_48).
car(edsel_57).
This program says that red cars or blue bikes are fun. It then goes on to name some examples of each type of object.
14
Query Example 2
Let's now ask what is a fun item. To do this we first must enter the following query
?- fun(What).
We first attempt to prove something is fun. To do this we have two clauses of fun that we can use
/* clause 1 */
fun(X) :-
red(X),
car(X).
/* clause 2 */
fun(X) :-
blue(X),
bike(X).
Using the first clause, we can now look to see if we can find some red object
15
Query Example 3
red(block_1).
red(car_27).
The first red item that we find is apple_1. This gives us the instantiation X=apple_1. Next we have to see if this is a
car, so we ask the question car(apple_1). Does this match with our existing database of facts about cars?
/* database of cars */
car(desoto_48).
car(edsel_57).
The answers is that it will not. apple_1 will not match with desoto_48 or with edsel_57. So what do we do next? In
such cases, Prolog backtracks in order to find another solution. Thus we go back to the red database, and see if we
can find another possible solution. In this case we can
red(apple_1).
red(car_27).
We can use the next clause and see if block_1 is a red car.
16
Query Example 4
Returning to our cars database, again block_1 will not match against desoto_48 or with edsel_57. In this case we
must backtrack a second time to see if there is another red item that we can find. This time we find the third clause in
the database, and use that.
red(apple_1).
red(block_1).
We now attempt the car goal again, but this time for car_27. Now recall what we said earlier about the dangers of
over estimating the powers of Prolog. Prolog will try to match the goal car(car_27) against the database
car(desoto_48).
car(edsel_57).
This will fail. car_27 does not match with either desoto_48 or edsel_57. Although the programmer probably meant
car_27 to mean car number 27 to be a car, Prolog has no way of gleaning this intended semantics, and fails the call
based on a simple pattern match.
Well, what do we do now. The first step is to go back and see if there are any more possible red items. Well
we have now exhausted all three choices, hence there are no more red items to choose from. Given that there
are no more possible ways that we could satisfy clause 1, we now more on to clause 2, as we see on the next
card.
17
Query Example 5
Let us recall our program. It said that something was fun if it was red and a car or blue and a bike. Well, we couldn't
find something that was both red and a car, so we now see if something blue and a bike. Let's recap the code:
/* clause 1 */
red(X),
car(X).
/* clause 2 */
blue(X),
bike(X).
Thus we will now try to prove that something is fun, by trying to find something that is blue, using the blue database:
blue(flower_3).
blue(glass_9).
blue(honda_81).
18
Query Example 6
We now have to prove the goal bike(flower_3) against our bike database:
bike(iris_8).
bike(my_bike).
bike(honda_81).
This again must fail, since flower_3 does not match against iris_8, my_bike, or honda_81. So as before, we must
backtrack and find an alternate blue item. Recall our database:
blue(flower_3).
blue(glass_9).
blue(honda_81).
We have tried flower_3 without luck, so next we try the second blue fact, blue(glass_9). We now try goal
bike(glass_9). Again this fails to unify. As a result we must go back yet again and find another blue object. This time
we will use the third clause of blue, blue(honda_81). We will now give us the goal bike(honda_81). This matches
clause three!. So at last we have found something which is blue and a bike. As a result we can now conclude that it is
fun, Prolog then displays the following:
?- fun(What).
What=honda_81
yes
19
If the user presses the semicolon (;) key, the listener looks for other solutions. First it unbinds the variable X. Next it
resumes the search using the clause following the one that had just satisfied the query. This is called backtracking.
20
21
6. Recursion
Recursion
As is commonly the case in many programming tasks, we often wish to repeatedly perform
some operation either over a whole data-structure, or until a certain point is reached. The way
we typically do this in Prolog is by recursion. This simply means a program calls itself typically
until some final point is reached. Frequently in Prolog what this means is that we have a first
fact that acts as some stopping condition followed up by some rule(s) that performs some
operation before reinvoking itself.
Recursion is often found to be a hard concept to grasp. For this reason we will present two
detailed examples of how it works.
Recursion 1
on_route(rome).
on_route(Place):-
move(Place,Method,NewPlace),
on_route(NewPlace).
move(home,taxi,halifax).
move(halifax,train,gatwick).
move(gatwick,plane,rome).
on_route is a recursive predicate. This program sees if it is possible to travel to rome from a
particular place. The first clause sees if we have already reached rome, in which case we stop.
22
The second clause sees if there is a move from the current place, to somewhere new, and then
recursive sees if the NewPlace is on_route to rome. The database of moves that we can make is
on the right.
Let's now consider what happens when we pose the query ?- on_route(home). This matches
clause two of on_route (it can't match clause one because home and rome don't unify). The
second on_route clause consists of two subgoals. The first asks whether you can move from
home to some new location i.e. move(home,Method,NewPlace). This succeeds with Method =
taxi, NewPlace = halifax. This says that yes we can move from home by taking a taxi to halifax.
Next we recursively see if we can find a route from halifax to rome by doing the same thing
again. This is done by executing the new subgoal on_route(halifax).
Recursion 2
on_route(rome).
move(home,taxi,halifax).
move(halifax,train,gatwick).
move(gatwick,plane,rome).
The goal on_route(halifax) will fail to unify on clause one, so again we'll use the recursive clause
two and find some new place to go to. Hence we try the goal move(halifax,Method,NewPlace)
this succeeds because we can catch a train from halifax to gatwick airport. Hence Method=train,
NewPlace=gatwick. As a result we then try the recursive call on_route(gatwick), i.e. we see if
there is a move from gatwick which will get us to rome.
We now try on_route(gatwick), again this only unifies with the second clause. As a result we try
the move clause again this time with Place bound to gatwick. This query will match the third
clause of the move database. The results in Method=plane, NewPlace=rome. Next we try the
recursive goal on_route(rome). This now matches clause one of on_route. This is just a fact and
succeeds. As a result all the other on_route goals in turn succeed. Thus finally our first goal ?-
on_route(home) succeeds and Prolog responds yes
23
Recursion 3
parent(john,paul). /* paul is john's parent */
The above program finds ancestors, by trying to link up people according to the database of
parents at the top to the card. So let's try it out by asking
?- ancestor(john,tom).
The first clause of ancestor looks to see if there exists a clause that could match the goal
parent(john,tom). This fails to match, as a result we try the second clause of ancestor. We now
pose the query parent(john,Z). This results in us choosing clause one of parent and binding
Z=paul. As a result, we now pose the recursive query ancestor(paul,tom). Applying the ancestor
rule again, we first try the first clause. This means we check for parent(paul,tom). which
successfully matches the second clause of parent. As a result the goal ancestor(paul,tom)
succeeds. This in turn leads to the goal ancestor(john,tom) succeeding and Prolog responding
yes
24
7. List
So far we have only considered simple items as arguments to our programs. However in Prolog
a very common data-structure is the list.
Lists themselves have the following syntax. They always start and end with square brackets, and
each of the items they contain is separated by a comma. Here is a simple list
[a,freddie,A_Variable,apple]
Prolog also has a special facility to split the first part of the list (called the head) away from the
rest of the list (known as the tail). We can place a special symbol | (pronounced 'bar') in the list to
distinguish between the first item in the list and the remaining list. For example, consider the
following.
[first,second,third] = [A|B]
where A = first and B=[second,third]
The unification here succeeds. A is bound to the first item in the list, and B to the remaining list.
List Examples 1
Here are some example simple lists
[a,b,c,d,e,f,g]
[apple,pear,bananas,breadfruit]
25
List Example 2
Consider the following fact.
p([H|T], H, T).
?- p([a,b,c], X, Y).
X=a
Y=[b,c]
yes
?- p([a], X, Y).
X=a
Y=[]
yes
?- p([], X, Y).
no
List Searching
We can use lists within facts and rules. One common way of using lists is to store information
within a list and then subsequently search for this information when we run our programs. In
order to search a list, Prolog inspects the first item in a list and then goes on to repeat the same
process on the rest of the list. This is done by using recursion. The search can either stop when
we find a particular item at the start of the list or when we have searched the whole list, in
which case the list to be searched will be the empty list. In order to do this, we have to be able
to selectively pull the list apart. We have already seen how we can go about doing this. In the
previous section we showed how to take the head and a tail of a list:
[Head|Tail]
26
This method constitutes the basis of the searching method. We shall use it to pull apart a list,
looking at the first item each time, recursively looking at the tail, until we reach the empty list
[ ], when we will stop.
Otherwise we could prove something was on a list if we could prove that although it didn't
match the existing head of the list, it nonetheless would match another head of the list if we
disregarded the first item and just considered the rest of the list i.e.
on(Item,[DisregardHead|Tail]):- on(Item,Tail).
We now have a program consisting of a fact and a rule for testing if something is on a rule. To
recap, it sees if something is the first item in the list. If it is we succeed. If it is not, then we
throw away the first item in the list and look at the rest.
on(Item,[DisregardHead|Tail]):- on(Item,Tail).
Let's go through the last example again in more detail. Suppose we pose the query:
The first clause of on requires the first argument of on to match with the list head. However
apples and pears do not match. Thus we must move on to the second clause. This splits the list
up as follows:
DisregardHead = pears
Tail = [tomatoes,apples,grapes]
This now gives us the following goal in the body of our rule:
27
on(apples, [tomatoes, apples, grapes]).
Again, we see if apples and tomatoes match using our initial facts. Since they don't, we again
use our second clause to strip off the current list head, giving us the new goal: on(apples,
[apples,grapes]). Since apples matches apples our first clause succeeds as does our query.
[london_buckingham_palace,
paris_eiffel_tower,
york_minster,
pisa_leaning_tower,
athens_parthenon]
Write a program called member, that would be able to see if something was a member of a list.
The program would inform me that sydney_opera_house was not on list by failing, yet confirm
all the above were on the list by succeeding (answering yes).
List Construction.
The last example showed how we could strip off the front items on a list and recursively search
the rest. We can also use the same method to build lists. For example take one existing list, say
List1. We can make a new list List2 which contains List1 but with the new head prolog, as
follows:
List2 = [prolog|List1]
This again is pure unification. List2 now consists of the head prolog plus whatever List1 was
bound to. If List1 were to be bound e.g. List1 = [list,c,pascal,basic], List2 would now be the list
[prolog,lisp,c,pascal,basic]
We can use this construction technique to build lists during recursion. We'll present three
examples of this in the forthcoming cards.
28
two are lists. The predicate uses these two lists to produce a third list which combines the
original two. For example,
?- append([a,b,c],[one,two,three],Result).
Result = [a,b,c,one,two,three]
The way we do this in Prolog uses both list construction and list deconstruction. Recall that on
the previous card we saw how we could glue an item onto the front of a list to produce a new
list. Thus we can produce the list [c,one,two,three] from the list [one,two three] by saying
NewList = [c|[one,two,three]. This results in NewList being bound to the list [c,one,two,three].
This is the clue to how we write append in Prolog. We can recursively split the first list into
single items by the deconstruction techniques discussed earlier. We can then use the
construction method above to glue together a new list, which we shall illustrate next.
append([Head|Tail],List2,[Head|Result]):- append(Tail,List2,Result).
Let's now follow the program as it executes. Using the second rule we first reduce the query to
29
append([b,c],[one,two,three],Result)
and then to
append([c],[one,two,three],Result)
and finally to
append([],[one,two,three],Result).
This final clause can match against the initial fact, giving append([],[one,two,three],
[one,two,three]).
Since this is a fact, this terminates the recursion. As we pop out of each recursive step we then
add on (respectively) c,b, and a to the list, giving the list [a,b,c.one,two,three].
sift([],[]).
Next we need to say that if the item passes our test, put it in the output list
Otherwise we are will disgard the head and look for other hits in the tail
30
List Constructions Example 5
Let us now see how our program respond to the query
?- sift([1,12,3,14,5,8], Result).
To this goal, we first unify with clause 2. The result is to evaluate the subgoal 1 > 6, this clearly
fails, so we move on to the third clause and try the goal sift([12,3,14,5,8],Result). This again
matches on the second clause of sift. We now try the subgoal 12>6, this succeeds and we now
attempt the goal sift([3,14,5,8],Result). However, notice what also happens. By using this clause
we also place 12 on our output list.
Stepping further through our example, we see that greater than subgoal in clause 2 succeeds for
14 and 8, till finally we get the goal sift([],Result). This matches the first goal, with Result bound
to []. As we come out of the recursion, we see that clause 2 builds up the result for to the list [8],
then [14,8], then finally [12,14,8], before the query finally succeeds.
31