0% found this document useful (0 votes)
204 views

Learn Prolog Now 03 5062

This document introduces recursive definitions in Prolog and discusses the difference between a Prolog program's declarative and procedural meanings. It provides an example knowledge base that defines the predicates is_digesting/2 and just_ate/2 recursively. Declaratively, the definition states that an entity is digesting something if it just ate that thing or if it just ate something that is digesting that thing. Procedurally, Prolog uses the recursive definition to break the problem into subgoals until base cases can be resolved by the facts in the knowledge base. The example query demonstrates how Prolog applies the recursive rules to find that the stork is digesting the mosquito.

Uploaded by

Van Tuan Nguyen
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
204 views

Learn Prolog Now 03 5062

This document introduces recursive definitions in Prolog and discusses the difference between a Prolog program's declarative and procedural meanings. It provides an example knowledge base that defines the predicates is_digesting/2 and just_ate/2 recursively. Declaratively, the definition states that an entity is digesting something if it just ate that thing or if it just ate something that is digesting that thing. Procedurally, Prolog uses the recursive definition to break the problem into subgoals until base cases can be resolved by the facts in the knowledge base. The example query demonstrates how Prolog applies the recursive rules to find that the stork is digesting the mosquito.

Uploaded by

Van Tuan Nguyen
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 18

32

Chapter 2. Matching and Proof Search Draw the search tree for the fth query magic(Hermione). Exercise 2.3 Here is a tiny lexicon and mini grammar with only one rule which denes a sentence as consisting of ve words: an article, a noun, a verb, and again an article and a noun.
word(article,a). word(article,every). word(noun,criminal). word(noun,big kahuna burger). word(verb,eats). word(verb,likes). sentence(Word1,Word2,Word3,Word4,Word5) :word(article,Word1), word(noun,Word2), word(verb,Word3), word(article,Word4), word(noun,Word5).

What query do you have to pose in order to nd out which sentences the grammar can generate? List all sentences that this grammar can generate in the order Prolog will generate them. Make sure that you understand why Prolog generates them in this order. Exercise 2.4 Here are six English words: abalone, abandon, anagram, connect, elegant, enhance. They are to be arranged in a crossword puzzle like fashion in the grid given below.

The following knowledge base represents a lexicon containing these words.


word(abalone,a,b,a,l,o,n,e). word(abandon,a,b,a,n,d,o,n). word(enhance,e,n,h,a,n,c,e). word(anagram,a,n,a,g,r,a,m). word(connect,c,o,n,n,e,c,t). word(elegant,e,l,e,g,a,n,t).

2.4. Practical Session 2

33

Write a predicate crosswd/6 that tells us how to ll the grid, i.e. the rst three arguments should be the vertical words from left to right and the following three arguments the horizontal words from top to bottom.

2.4

Practical Session 2
By this stage, you should have had your rst taste of running Prolog programs. The purpose of the second practical session is to suggest two sets of keyboard exercises which will help you get familiar with the way Prolog works. The rst set has to do with matching , the second with proof search. First of all, start up your Prolog interpreter. That is, get a screen displaying the usual Im ready to start prompt, which probably looks something like:
?-

Now verify your answers to Exercise 1.1, the matching examples. You dont need to consult any knowledge bases, simply ask Prolog directly whether it is possible to unify the terms by using the built-in =/2 predicate. For example, to test whether food(bread,X) and food(Y,sausage) unify, just type in
food(bread,X) = food(Y,sausage).

and hit return. You should also look at what happens when Prolog gets locked into an attempt to match terms that cant be matched because it doesnt carry out an occurs check. For example, see what happens when you give it the following query:
g(X,Y) = Y.

Ah yes! This is the perfect time to make sure you know how to abort a program that is running wild! Well, once youve gured that out, its time to move onto something new. There is another important built-in Prolog predicate for answering queries about matching, namely \=/2 (that is: a 2-place predicate \=). Roughly speaking, this works in the opposite way to the =/2 predicate: it succeeds when its two arguments do not unify. For example, the terms a and b do not unify, which explains the following dialogue:
a \= b yes

Make sure you understand the way \=/2 predicate works by trying it out on (at least) the following examples. But do this actively, not passively. That is, after you type in an example, pause, and try to work out for yourself what Prolog is going to respond. Only then hit return to see if you are right. 1. a \= a

34 2. a \= a 3. A \= a 4. f(a) \= a 5. f(a) \= A 6. f(A) \= f(a) 7. g(a,B,c) \= g(A,b,C) 8. g(a,b,c) \= g(A,C) 9. f(X) \= X

Chapter 2. Matching and Proof Search

Thus the \=/2 predicate is (essentially) the negation of the =/2 predicate: a query involving one of these predicates will be satised when the corresponding query involving the other is not, and vice versa (this is the rst example we have seen of a Prolog mechanism for handling negation). But note that word essentially. Things dont work out quite that way, as you will realise if you think about the trickier examples youve just tried out... Its time to move on and introduce one of the most helpful tools in Prolog: trace. This is an built-in Prolog predicate that changes the way Prolog runs: it forces Prolog to evaluate queries one step at a time, indicating what it is doing at each step. Prolog waits for you to press return before it moves to the next step, so that you can see exactly what is going on. It was really designed to be used as a debugging tool, but its also really helpful when youre learning Prolog: stepping through programs using trace is an excellent way of learning how Prolog proof search works. Lets look at an example. In the lecture, we looked at the proof search involved when we made the query k(X) to the following knowledge base:
f(a). f(b). g(a). g(b). h(b). k(X) :- f(X),g(X),h(X).

Suppose this knowledge base is in a le proof.pl. We rst consult it:


1 ?- [proof]. % proof compiled 0.00 sec, 1,524 bytes yes

We then type trace. and hit return:

2.4. Practical Session 2


2 ?- trace. Yes

35

Prolog is now in trace mode, and will evaluate all queries step by step. For example, if we pose the query k(X), and then hit return every time Prolog comes back with a ?, we obtain (something like) the following:
[trace] 2 ?Call: (6) Call: (7) Exit: (7) Call: (7) Exit: (7) Call: (7) Fail: (7) Fail: (7) Redo: (7) Exit: (7) Call: (7) Exit: (7) Call: (7) Exit: (7) Exit: (6) X = b Yes k(X). k(_G348) ? f(_G348) ? f(a) ? g(a) ? g(a) ? h(a) ? h(a) ? g(a) ? f(_G348) ? f(b) ? g(b) ? g(b) ? h(b) ? h(b) ? k(b) ?

Study this carefully. That is, try doing the same thing yourself, and try to relate this output to the discussion of the example in the text. To get you started, well remark that the third line is where the variable in the query is (wrongly) instantiated to a, and that the line marked redo is when Prolog realizes its taken the wrong path, and backtracks to instantiate the variable to b. While learning Prolog, use trace, and use it heavily. Its a great way to learn. Oh yes: you also need to know how to turn trace off. Simply type notrace. and hit return:
notrace.

36

Chapter 2. Matching and Proof Search

Recursion
This lecture has two main goals: 1. To introduce recursive denitions in Prolog. 2. To show that there can be mismatches between the declarative meaning of a Prolog program, and its procedural meaning.

3.1

Recursive denitions
Predicates can be dened recursively. Roughly speaking, a predicate is recursively dened if one or more rules in its denition refers to itself.

3.1.1

Example 1: Eating
Consider the following knowledge base:
is_digesting(X,Y) :- just_ate(X,Y). is_digesting(X,Y) :just_ate(X,Z), is_digesting(Z,Y). just_ate(mosquito,blood(john)). just_ate(frog,mosquito). just_ate(stork,frog).

At rst glance this seems pretty ordinary: its just a knowledge base containing two facts and two rules. But the denition of the is_digesting/2 predicate is recursive. Note that is_digesting is (at least partially) dened in terms of itself, for the is_digesting functor occurs on both the left and right hand sides of the second rule. Crucially, however, there is an escape from this circularity. This is provided by the just_ate predicate, which occurs in both the rst and second rules. (Signicantly, the right hand side of the rst rule makes no mention of is_digesting.) Lets now consider both the declarative and procedural meanings of this rule. The word declarative is used to talk about the logical meaning of Prolog knowledge bases. That is, the declarative meaning of a Prolog knowledge base is simply what

38

Chapter 3. Recursion it says, or what it means, if we read it as a collection of logical statements. And the declarative meaning of this recursive denition is fairly straightforward. The rst clause (the escape clause, the one that is not recursive, or as we shall usually call it, the base clause), simply says that: if X has just eaten Y, then X is now digesting Y. This is obviously a sensible denition. So what about the second clause, the recursive clause? This says that: if X has just eaten Z and Z is digesting Y, then X is digesting Y, too. Again, this is obviously a sensible denition. So now we know what this recursive denition says, but what happens when we pose a query that actually needs to use this denition? That is, what does this denition actually do? To use the normal Prolog terminology, what is its procedural meaning? This is also reasonably straightforward. The base rule is like all the earlier rules weve seen. That is, if we ask whether X is digesting Y, Prolog can use this rule to ask instead the question: has X just eaten Y? What about the recursive clause? This gives Prolog another strategy for determining whether X is digesting Y: it can try to nd some Z such that X has just eaten Z, and Z is digesting Y. That is, this rule lets Prolog break the task apart into two subtasks. Hopefully, doing so will eventually lead to simple problems which can be solved by simply looking up the answers in the knowledge base. The following picture sums up the situation: just_ate just_ate is_digesting X Y is_digesting Lets see how this works. If we pose the query:
?- is_digesting(stork,mosquito).

is_digesting

then Prolog goes to work as follows. First, it tries to make use of the rst rule listed concerning is_digesting; that is, the base rule. This tells it that X is digesting Y if X just ate Y, By unifying X with stork and Y with mosquito it obtains the following goal:
just_ate(stork,mosquito).

But the knowledge base doesnt contain the information that the stork just ate the mosquito, so this attempt fails. So Prolog next tries to make use of the second rule. By unifying X with stork and Y with mosquito it obtains the following goals:
just_ate(stork,Z), is_digesting(Z,mosquito).

That is, to show is_digesting(stork,mosquitp)}, Prolog needs to nd a value for Z such that, rstly,
just_ate(stork,Z).

3.1. Recursive denitions and secondly,


is_digesting(Z,mosquito).

39

And there is such a value for Z, namely frog. It is immediate that


just_ate(stork,frog).

will succeed, for this fact is listed in the knowledge base. And deducing
is_digesting(frog,mosquito).

is almost as simple, for the rst clause of is_digesting/2 reduces this goal to deducing
just_ate(frog,mosquito).

and this is a fact listed in the knowledge base. Well, thats our rst example of a recursive rule denition. Were going to learn a lot more about them in the next few weeks, but one very practical remark should be made right away. Hopefully its clear that when you write a recursive predicate, it should always have at least two clauses: a base clause (the clause that stops the recursion at some point), and one that contains the recursion. If you dont do this, Prolog can spiral off into an unending sequence of useless computations. For example, heres an extremely simple example of a recursive rule denition:
p :- p.

Thats it. Nothing else. Its beautiful in its simplicity. And from a declarative perspective its an extremely sensible (if rather boring) denition: it says if property p holds, then property p holds. You cant argue with that. But from a procedural perspective, this is a wildly dangerous rule. In fact, we have here the ultimate in dangerous recursive rules: exactly the same thing on both sides, and no base clause to let us escape. For consider what happens when we pose the following query:
?- p.

Prolog asks itself: how do I prove p? And it realizes, Hey, Ive got a rule for that! To prove p I just need to prove p!. So it asks itself (again): how do I prove p? And it realizes, Hey, Ive got a rule for that! To prove p I just need to prove p!. So it asks itself (yet again): how do I prove p? And it realizes, Hey, Ive got a rule for that! To prove p I just need to prove p! So then it asks itself (for the fourth time): how do I prove p? And it realizes that... If you make this query, Prolog wont answer you: it will head off, looping desperately away in an unending search. That is, it wont terminate, and youll have to interrupt it. Of course, if you use trace, you can step through one step at a time, until you get sick of watching Prolog loop.

40

Chapter 3. Recursion

3.1.2

Example 2: Descendant
Now that we know something about what recursion in Prolog involves, it is time to ask why it is so important. Actually, this is a question that can be answered on a number of levels, but for now, lets keep things fairly practical. So: when it comes to writing useful Prolog programs, are recursive denitions really so important? And if so, why? Lets consider an example. Suppose we have a knowledge base recording facts about the child relation:
child(charlotte,caroline). child(caroline,laura).

That is, Caroline is a child of Charlotte, and Laura is a child of Caroline. Now suppose we wished to dene the descendant relation; that is, the relation of being a child of, or a child of a child of, or a child of a child of a child of, or.... Heres a rst attempt to do this. We could add the following two non-recursive rules to the knowledge base:
descend(X,Y) :- child(X,Y). descend(X,Y) :- child(X,Z), child(Z,Y).

Now, fairly obviously these denitions work up to a point, but they are clearly extremely limited: they only dene the concept of descendant-of for two generations or less. Thats ok for the above knowledge base, but suppose we get some more information about the child-of relation and we expand our list of child-of facts to this:
child(martha,charlotte). child(charlotte,caroline). child(caroline,laura). child(laura,rose).

Now our two rules are inadequate. For example, if we pose the queries
?- descend(martha,laura).

or
?- descend(charlotte,rose).

we get the answer No!, which is not what we want. Sure, we could x this by adding the following two rules:
descend(X,Y) :- child(X,Z_1), child(Z_1,Z_2), child(Z_2,Y). descend(X,Y) :- child(X,Z_1), child(Z_1,Z_2), child(Z_2,Z_3), child(Z_3,Y).

3.1. Recursive denitions

41

But, lets face it, this is clumsy and hard to read. Moreover, if we add further child-of facts, we could easily nd ourselves having to add more and more rules as our list of child-of facts grow, rules like:
descend(X,Y) :- child(X,Z_1), child(Z_1,Z_2), child(Z_2,Z_3), . . . child(Z_17,Z_18). child(Z_18,Z_19). child(Z_19,Y).

This is not a particularly pleasant (or sensible) way to go! But we dont need to do this at all. We can avoid having to use ever longer rules entirely. The following recursive rule xes everything exactly the way we want:
descend(X,Y) :- child(X,Y). descend(X,Y) :- child(X,Z), descend(Z,Y).

What does this say? The declarative meaning of the base clause is: if Y is a child of Y, then Y is a descendant of X. Obviously sensible. So what about the recursive clause? Its declarative meaning is: if Z is a child of X, and Y is a descendant of Z, then Y is a descendant of X. Again, this is obviously true. So lets now look at the procedural meaning of this recursive predicate, by stepping through an example. What happens when we pose the query:
descend(martha,laura)

Prolog rst tries the rst rule. The variable X in the head of the rule is unied with martha and Y with laura and the next goal Prolog tries to prove is
child(martha,laura)

This attempt fails, however, since the knowledge base neither contains the fact child(martha,laura) nor any rules that would allow to infer it. So Prolog backtracks and looks for an alternative way of proving descend(martha,laura). It nds the second rule in the knowledge base and now has the following subgoals:
child(martha,_633), descend(_633,laura).

Prolog takes the rst subgoal and tries to match it onto something in the knowledge base. It nds the fact child(martha,charlotte) and the Variable _633 gets instantiated to charlotte. Now that the rst subgoal is satised, Prolog moves to the second subgoal. It has to prove

42
descend(charlotte,laura)

Chapter 3. Recursion

This is the recursive call of the predicate descend/2. As before, Prolog starts with the rst rule, but fails, because the goal
child(charlotte,laura)

cannot be proved. Backtracking, Prolog nds that there is a second possibility to be checked for descend(charlotte,laura), viz. the second rule, which again gives Prolog two new subgoals:
child(charlotte,_1785), descend(_1785,laura).

The rst subgoal can be unied with the fact child(charlotte,caroline) of the knowledge base, so that the variable _1785 is instantiated with caroline. Next Prolog tries to prove
descend(caroline,laura).

This is the second recursive call of predicate descend/2. As before, it tries the rst rule rst, obtaining the following new goal:
child(caroline,laura)

This time Prolog succeeds, since child(caroline,laura) is a fact in the database. Prolog has found a proof for the goal descend(caroline,laura) (the second recursive call). But this means that child(charlotte,laura) (the rst recursive call) is also true, which means that our original query descend(martha,laura) is true as well. Here is the search tree for the query descend(martha,laura). Make sure that you understand how it relates to the discussion in the text; i.e. how Prolog traverses this search tree when trying to prove this query.

3.1. Recursive denitions

43

descend(martha,laura)

child(martha, laura)

child(martha,_G490), descend(_G490,laura) _G490 = charlotte descend(charlotte,laura)

child(charlotte,laura)

child(charlotte,_G494), descend(_G494, laura) _G494 = caroline descend(caroline,laura)

child(caroline,laura)

It should be obvious from this example that no matter how many generations of children we add, we will always be able to work out the descendant relation. That is, the recursive denition is both general and compact: it contains all the information in the previous rules, and much more besides. In particular, the previous lists of non-recursive rules only dened the descendant concept up to some xed number of generations: we would need to write down innitely many non-recursive rules if we wanted to capture this concept fully, and of course thats impossible. But, in effect, thats what the recursive rule does for us: it bundles up all this information into just three lines of code. Recursive rules are really important. They enable to pack an enormous amount of information into a compact form and to dene predicates in a natural way. Most of the work you will do as a Prolog programmer will involve writing recursive rules.

3.1.3

Example 3: Successor
In the previous lectures we remarked that building structure through matching is a key idea in Prolog programming. Now that we know about recursion, we can give more interesting illustrations of this. Nowadays, when human beings write numerals, they usually use decimal notation (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, and so on) but as you probably know, there are many other notations. For example, because computer hardware is generally based on digital circuits, computers usually use binary notation to represent numerals (0, 1, 10, 11, 100, 101, 110, 111, 1000, and so on), for the 0 can be implemented as as switch being off, the 1 as a switch being on. Other cultures use different systems. For example, the ancient Babylonians used a base 64 system, while the ancient Romans used a rather ad-hoc system (I, II, III, IV, V, VI, VII, VIII, IX, X). This last example

44

Chapter 3. Recursion shows that notational issues can be important. If you dont believe this, try guring out a systematic way of doing long-division in Roman notation. As youll discover, its a frustrating task. In fact, the Romans had a group of professionals (analogs of modern accountants) who specialized in this. Well, heres yet another way of writing numerals, which is sometimes used in mathematical logic. It makes use of just four symbols: 0, succ, and the left and right brackets. This style of numeral is dened by the following inductive denition: 1. 0 is a numeral. 2. If X is a numeral, then so is succ(X). As is probably clear, succ can be read as short for successor. That is, succ(X) represents the number obtained by adding one to the number represented by X. So this is a very simple notation: it simply says that 0 is a numeral, and that all other numerals are built by stacking succ symbols in front. (In fact, its used in mathematical logic because of this simplicity. Although it wouldnt be pleasant to do household accounts in this notation, it is a very easy notation to prove things about.) Now, by this stage it should be clear that we can turn this denition into a Prolog program. The following knowledge base does this:
numeral(0). numeral(succ(X)) :- numeral(X).

So if we pose queries like


numeral(succ(succ(succ(0)))).

we get the answer yes. But we can do some more interesting things. Consider what happens when we pose the following query:
numeral(X).

That is, were saying Ok, show me some numerals. Then we can have the following dialogue with Prolog:
X = 0 ; X = succ(0) ; X = succ(succ(0)) ; X = succ(succ(succ(0))) ; X = succ(succ(succ(succ(0)))) ; X = succ(succ(succ(succ(succ(0))))) ;

3.1. Recursive denitions


X = succ(succ(succ(succ(succ(succ(0)))))) ; X = succ(succ(succ(succ(succ(succ(succ(0))))))) ; X = succ(succ(succ(succ(succ(succ(succ(succ(0)))))))) ;

45

X = succ(succ(succ(succ(succ(succ(succ(succ(succ(0))))))))) ; X = succ(succ(succ(succ(succ(succ(succ(succ(succ(succ(0)))))))))) yes

Yes, Prolog is counting: but whats really important is how its doing this. Quite simply, its backtracking through the recursive denition, and actually building numerals using matching. This is an instructive example, and it is important that you understand it. The best way to do so is to sit down and try it out, with trace turned on. Building and binding. Recursion, matching, and proof search. These are ideas that lie at the heart of Prolog programming. Whenever we have to generate or analyze recursively structured objects (such as these numerals) the interplay of these ideas makes Prolog a powerful tool. For example, in the next lecture we introduce lists, an extremely important recursive data structure, and we will see that Prolog is a natural list processing language. Many applications (computational linguistics is a prime example) make heavy use of recursively structured objects, such as trees and feature structures. So its not particularly surprising that Prolog has proved useful in such applications.

3.1.4

Example 3: Addition
As a nal example, lets see whether we can use the representation of numerals that we introduced in the previous section for doing simple arithmetic. Lets try to dene addition. That is, we want to dene a predicate add/3 which when given two numerals as the rst and second argument returns the result of adding them up as its third argument. E.g.
?- add(succ(succ(0)),succ(succ(0)),succ(succ(succ(succ(0))))). yes ?- add(succ(succ(0)),succ(0),Y). Y = succ(succ(succ(0)))

There are two things which are important to notice: 1. Whenever the rst argument is 0, the third argument has to be the same as the second argument:
?- add(0,succ(succ(0)),Y). Y = succ(succ(0)) ?- add(0,0,Y). Y = 0

This is the case that we want to use for the base clause.

46

Chapter 3. Recursion 2. Assume that we want to add the two numerals X and Y (e.g. succ(succ(succ(0))) and succ(succ(0))) and that X is not 0. Now, if X is the numeral that has one succ functor less than X (i.e. succ(succ(0)) in our example) and if we know the result lets call it Z of adding X and Y (namely succ(succ(succ(succ(0))))), then it is very easy to compute the result of adding X and Y: we just have to add one succ-functor to Z. This is what we want to express with the recursive clause. Here is the predicate denition that expresses exactly what we just said:
add(0,Y,Y). add(succ(X),Y,succ(Z)) :add(X,Y,Z).

So, what happens, if we give Prolog this predicate denition and then ask:
add(succ(succ(succ(0))), succ(succ(0)), R)

Lets go through the way Prolog processes this query step by step. The trace and the search tree are given below. The rst argument is not 0 which means that only the second clause for add matches. This leads to a recursive call of add. The outermost succ functor is stripped off the rst argument of the original query, and the result becomes the rst argument of the recursive query. The second argument is just passed on to the recursive query, and the third argument of the recursive query is a variable, the internal variable _G648 in the trace given below. _G648 is not instantiated, yet. However, it is related to R (which is the variable that we had as third argument in the original query), because R was instantiated to succ(_G648), when the query was matched to the head of the second clause. But that means that R is not a completely uninstantiated variable anymore. It is now a complex term, that has a (uninstantiated) variable as its argument. The next two steps are essentially the same. With every step the rst argument becomes one level smaller. The trace and the search tree show this nicely. At the same time one succ functor is added to R with every step, but always leaving the argument of the innermost variable uninstantiated. After the rst recursive call R is succ(_G648), in the second recursive call _G648 is instantiated with succ(_G650), so that R is succ(succ(_G650), in the third recursive call _G650 is instantiated with succ(_G652) and R therefore becomes succ(succ(succ(_G652))). The search tree shows this step by step instantiation. At some point all succ functors have been stripped off the rst argument and we have reached the base clause. Here, the third argument is equated with the second argument, so that "the hole" in the complex term R is nally lled. This is a trace for the query add(succ(succ(succ(0))), succ(succ(0)), R):
Call: (6) add(succ(succ(succ(0))), succ(succ(0)), R) Call: (7) add(succ(succ(0)), succ(succ(0)), _G648) Call: (8) add(succ(0), succ(succ(0)), _G650)

3.2. Clause ordering, goal ordering, and termination

47

Call: (9) add(0, succ(succ(0)), _G652) Exit: (9) add(0, succ(succ(0)), succ(succ(0))) Exit: (8) add(succ(0), succ(succ(0)), succ(succ(succ(0)))) Exit: (7) add(succ(succ(0)), succ(succ(0)), succ(succ(succ(succ(0)))))

Exit: (6) add(succ(succ(succ(0))), succ(succ(0)), succ(succ(succ(succ(succ(0)))

And here is the search tree for this query: add(succ(succ(succ(0))), succ(succ(0)), R) R = succ(_G648) add(succ(succ(0)), succ(succ(0)), _G648) _G648 = succ(_G650) add(succ(0), succ(succ(0)), _G650) _G650 = succ(_G652) add(0, succ(succ(0)), _G652) _G652 = succ(succ(0))

3.2

Clause ordering, goal ordering, and termination


Prolog was the rst reasonably successful attempt to make a logic programming language. Underlying logic programming is a simple (and seductive) vision: the task of the programmer is simply to describe problems. The programmer should write down (in the language of logic) a declarative specication (that is: a knowledge base), which describes the situation of interest. The programmer shouldnt have to tell the computer what to do. To get information, he or she simply asks the questions. Its up to the logic programming system to gure out how to get the answer. Well, thats the idea, and it should be clear that Prolog has taken some interesting steps in this direction. But Prolog is not, repeat not, a full logic programming language. If you only think about the declarative meaning of a Prolog program, you are in for a very tough time. As we learned in the previous lecture, Prolog has a very specic way of working out the answer to queries: it searches the knowledge base from top to bottom, clauses from left to right, and uses backtracking to recover from bad choices. These procedural aspects have an important inuence on what actually happens when

48

Chapter 3. Recursion you make a query. We have already seen a dramatic example of a mismatch between procedural and declarative meaning of a knowledge base (remember the p:- p program?), and as we shall now see, it is easy to dene knowledge bases with the same declarative meaning, but very different procedural meanings. Recall our earlier descendant program (lets call it descend1.pl):
child(martha,charlotte). child(charlotte,caroline). child(caroline,laura). child(laura,rose). descend(X,Y) :- child(X,Y). descend(X,Y) :- child(X,Z), descend(Z,Y).

Well make two changes to it, and call the result descend2.pl:
child(martha,charlotte). child(charlotte,caroline). child(caroline,laura). child(laura,rose). descend(X,Y) :- descend(Z,Y), child(X,Z). descend(X,Y) :- child(X,Y).

From a declarative perspective, what we have done is very simple: we have merely reversed the order of the two rules, and reversed the order of the two goals in the recursive clause. So, viewed as a purely logical denition, nothing has changed. We have not changed the declarative meaning of the program. But the procedural meaning has changed dramatically. For example, if you pose the query
descend(martha,rose).

you will get an error message (out of local stack, or something similar). Prolog is looping. Why? Well, to satisfy the query descend(martha,rose). Prolog uses the rst rule. This means that its next goal will be to satisfy the query
descend(W1,rose)

for some new variable W1. But to satisfy this new goal, Prolog again has to use the rst rule, and this means that its next goal is going to be
descend(W2,rose)

3.3. Exercises

49 for some new variable W2. And of course, this in turn means that its next goal is going to be descend(W3,rose) and then descend(W4,rose), and so on. In short, descend1.pl and descend2.pl are Prolog knowledge bases with the same declarative meaning but different procedural meanings: from a purely logical perspective they are identical, but they behave very differently. Lets look at another example. Recall out earlier successor program (lets call it numeral1.pl):
numeral(0). numeral(succ(X)) :- numeral(X).

Lets simply swap the order of the two clauses, and call the result numeral2.pl:
numeral(succ(X)) :- numeral(X). numeral(0).

Clearly the declarative, or logical, content of this program is exactly the same as the earlier version. But what about its behavior? Ok, if we pose a query about specic numerals, numeral2.pl will terminate with the answer we expect. For example, if we ask:
numeral(succ(succ(succ(0)))).

we will get the answer yes. But if we try to generate numerals, that is, if we give it the query
numeral(X).

the program wont halt. Make sure you understand why not. Once again, we have two knowledge bases with the same declarative meaning but different procedural meanings. Because the declarative and procedural meanings of a Prolog program can differ, when writing Prolog programs you need to bear both aspects in mind. Often you can get the overall idea (the big picture) of how to write the program by thinking declaratively, that is, by thinking simply in terms of describing the problem accurately. But then you need to think about how Prolog will actually evaluate queries. Are the rule orderings sensible? How will the program actually run? Learning to ip back and forth between procedural and declarative questions is an important part of learning to program in Prolog.

3.3

Exercises
Exercise 3.1 Do you know these wooden Russian dolls, where smaller ones are contained in bigger ones? Here is schematic picture of such dolls.

You might also like