4.8 KiB
Chapter 3: Recursion
Recursive definitions require that the recursive function isn't the first clause, ex:
is_digesting(X,Y) :- just_ate(X,Y).
is_digesting(X,Y) :-
just_ate(X,Z),
is_digesting(Z,Y).
Recursive definition require a base ("escape") clause in addition to the recursive clause.
Rule ordering, goal ordering, and termination
Underlying vision for Prolog (and logic programming in general): the programmer should be able to describe problems, and the computer derives the solution. The programmer builds a knowledge base, then asks question of the computer.
Prolog's search order:
- KB from top to bottom
- Clauses from left to right
- Backtracks on bad choices
This is somewhat of a procedural approach to declarative programming which may
affect the answer. Compare descend1
with descend2
:
child(anne,bridget).
child(bridget,caroline).
child(caroline,donna).
child(donna,emily).
descend1(X,Y) :- child(X,Y).
descend1(X,Y) :- child(X,Z),
descend1(Z,Y).
descend2(X,Y) :- descend2(Z,Y),
child(X,Z).
descend2(X,Y) :- child(X,Y).
Supposedly these give different answers, but swipl
exploded on the latter...
and continuing reading, that's the point --- a warning to avoid left-recursive
rules.
The takeaway is that you can get the overall idea of how to write the program by approaching the problem declaratively. Sensible goal orderings ensure program / query termination: place the recursive definition last.
Exercises
Exercise 3.1
In the text, we discussed the predicate
descend(X,Y) :- child(X,Y).
descend(X,Y) :- child(X,Z),
descend(Z,Y).
Suppose we reformulated this predicate as follows:
descend(X,Y) :- child(X,Y).
descend(X,Y) :- descend(X,Z),
descend(Z,Y).
Would this be problematic?
A: yes: there's no termination clause. See descend3
in descend.pl
.
Exercise 3.2
Do you know these wooden Russian dolls (Matryoshka dolls) where the smaller ones are contained in bigger ones?
Ex katarina(olga(natasha(irina))).
First, write a knowledge base using the predicate directlyIn/2
which encodes
which doll is directly contained in which other doll. Then, define a recursive
predicate in/2
, that tells us which doll is (directly or indirectly)
contained in which other dolls. For example, the query in(katarina,natasha)
should evaluate to true, while in(olga, katarina)
should fail.
directlyIn(natasha, irina).
directlyIn(olga, natasha).
directlyIn(katarina, olga).
in(X, Y) :- directlyIn(X, Y).
in(X, Y) :- directlyIn(X, Z),
in(Z, Y).
Exercise 3.3
We have the following knowledge base:
directTrain(saarbruecken,dudweiler).
directTrain(forbach,saarbruecken).
directTrain(freyming,forbach).
directTrain(stAvold,freyming).
directTrain(fahlquemont,stAvold).
directTrain(metz,fahlquemont).
directTrain(nancy,metz).
That is, this knowledge base holds facts about towns it is possible to travel
between by taking a direct train. But of course, we can travel further by
chaining together direct train journeys. Write a recursive predicate
travelFromTo/2
that tells us when we can travel by train between two towns.
For example, when given the query travelFromTo(nancy,saarbruecken)
it should
reply yes.
travelFromTo(X, Y) :- directTrain(X, Y).
travelFromTo(X, Y) :- directTrain(X, Z),
travelFromTo(Z, Y).
Exercise 3.4
Define a predicate greater_than/2
that takes two numerals in the notation
that we introduced in the text (that is, 0
, succ(0)
, succ(succ(0))
, and
so on) as arguments and decides whether the first one is greater than the
second one. For example:
?- greater_than(succ(succ(succ(0))),succ(0)).
yes
?- greater_than(succ(succ(0)),succ(succ(succ(0)))).
no
Answer (in succ.pl
):
greater_than(succ(succ(X)), succ(0)).
greater_than(succ(X), succ(Y)) :-
greater_than(X, Y).
Exercise 3.5
Binary trees are trees where all internal nodes have exactly two children. The
smallest binary trees consist of only one leaf node. We will represent leaf
nodes as leaf(Label)
. For instance, leaf(3)
and leaf(7)
are leaf nodes,
and therefore small binary trees. Given two binary trees B1 and B2 we can
combine them into one binary tree using the functor tree/2
as follows:
tree(B1,B2)
. So, from the leaves leaf(1)
and leaf(2)
we can build the
binary tree tree(leaf(1),leaf(2))
. And from the binary trees
tree(leaf(1),leaf(2))
and leaf(4)
we can build the binary tree
tree(tree(leaf(1), leaf(2)),leaf(4))
.
Now, define a predicate swap/2
, which produces the mirror image of the binary
tree that is its first argument. For example:
?- swap(tree(tree(leaf(1), leaf(2)), leaf(4)),T).
T = tree(leaf(4), tree(leaf(2), leaf(1))).
yes
This one took a lot of thinking, but the solution is in tree.pl
.