Recursion
Prof. Geraint A. Wiggins
Centre for Cognition, Computation and Culture
Goldsmiths College, University of London
Contents
• Recursion in general
• Recursion in Prolog
• Recursion on explicit structures
• Recursion on implicit structures
• Building recursive structures
Recursion in general
• Recursion is the idea of defining something in
terms of itself
• It is very closely bound up with the idea of
mathematical induction
• It allows us to make very clear statements of
algorithms. . .
• . . . because it is a very natural way to think
• Optimisations mean that programming recursively
can be efficient, too
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 2
Some Recursive Definitions
• In Prolog, a list is either an empty list or a term
connected by ‘.’ to another list
• Someone’s ancestor can be one of their parents or
an ancestor of one of their parents
• We can sort a list of numbers into order by picking
out the smallest, and then sorting the rest
• We can sort a list of numbers into order by picking
one, say X, dividing the rest into two groups,
bigger and smaller than X, and sorting the two
groups
• We can reverse a list of terms by taking the first
element off, reversing the rest, and putting the first
element on the end
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 3
Some Recursive Predicates
• Test if a term is a list
list( [] ).
list( [ |Tail] ) :- list( Tail ).
• Find an ancestor
ancestor( Old, Young ) :-
parent( Old, Young ).
ancestor( Old, Young ) :-
parent( Old, Middle ),
ancestor( Middle, Young ).
• Sort a list of numbers
sort( [], [] ).
sort( List, [Smallest|Sorted] ) :-
\+ List = [],
smallest( List, Smallest, Rest ),
sort( Rest, Sorted ).
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 4
Some Recursive Predicates
• Sort a list of numbers
sort( [], [] ).
sort( [First|Rest], Ans ) :-
split( First, Rest, Small, Big ),
sort( Small, Front ),
sort( Big, Back ),
append( Front, [First|Back], Ans ).
• Reverse a list
reverse( [], [] ).
reverse( [Head|Tail], Answer ) :-
reverse( Tail, RevTail ),
append( RevTail, [Head], Answer ).
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 5
Recursion in Prolog
• When we want to write recursive programs, we
need to think about two things:
1. How will the program terminate?
2. How will the program break up the data it
works on?
• Recursion is an example of a divide-and-conquer
strategy
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 6
Recursion in Prolog (2)
• To ensure that a program terminates, we must have
at least one base case – a non-recursive clause
• We must also ensure that something gets (in some
sense) ”reduced” each time a recursive step
happens, so that we can say when we have got to
the end
• Example – testing if a term is a list:
– The base case is when we have an empty list –
the smallest list possible
– The recursive case breaks down a non-empty
list into a head and a tail. . .
– . . . and then tests the tail, so the thing being
tested gets smaller each time.
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 7
Recursion on explicit structures
• Testing if a term is a list
list( [] ).
list( [ |Tail] ) :- list( Tail ).
?- list( [a,b,c] ).
Call: list( [a,b,c] ).
Unify: [ |Tail] = [a,b,c]
Call: list( [b,c] ).
Unify: [ |Tail’] = [b,c]
Call: list( [c] ).
Unify: [ |Tail’’] = [c]
Call: list( [] ).
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 8
Recursion on explicit structures (2)
• Example: a binary tree datatype:
– Leaves are of form leaf( value )
– Branches are of form
branch( Left, Right )
• Testing if a term is a binary tree
tree( leaf( X )).
tree( branch( X, Y )) :-
tree( X ),
tree( Y ).
• Note that we normally put the base case first, so
that Prolog tests it first!
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 9
Recursion on implicit structures
• An implicit datatype is one where there is no
visible term structure to control recursion
• Instead, the step along the datatype is defined by a
predicate, eg parent/2
• Example: parent/2
parent( john, mary ).
parent( anne, mary ).
parent( mary, dave ).
parent( tim, dave ).
• This forms an implicit binary tree, and we can use
it to control recursion just like an explicit one
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 10
Recursion on implicit structures (2)
• Example: ancestor/2
parent( john, mary ).
parent( anne, mary ).
parent( mary, dave ).
parent( tim, dave ).
ancestor( Old, Young ) :-
parent( Old, Young ).
ancestor( Old, Young ) :-
parent( Old, Middle ),
ancestor( Middle, Young ).
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 11
Recursion on implicit structures (3)
• Example run:
?- ancestor( john, dave ).
Call: ancestor( john, dave ).
Call: parent( john, dave ).
Fail.
Retry: ancestor( john, dave ).
Call: parent( john, Middle ).
Unify: Middle = mary.
Succeed: parent( john, mary ).
Call: ancestor( mary, dave ).
Call: parent( mary, dave ).
Succeed: parent( mary, dave ).
Succeed: ancestor( mary, dave ).
Succeed: ancestor( john, dave ).
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 12
Building recursive structures
• In Prolog, taking terms apart and building them
are nearly always the same thing.
• For example, in append/3:
append( [], List, List ).
append( [H|T], List, [H|BigList] ) :-
append( T, List, BigList ).
• Arguments 1 and 3 are both “broken down” or
“built up” by unification; we don’t usually use
both as inputs
• Exercise 2 shows how append/3 can be used
both to split up a list and to join lists together
• This is one reason why it is better not to think of
predicates as having “outputs” at all
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 13
Building recursive structures (2)
• In the examples so far, the answer has been built
up in reverse of the breakdown of the input (no
matter which way round they are)
• Alternatively, we can use an accumulator
argument to build up an answer as we break down
the input
• Example, reverse/3:
reverse( [], Answer, Answer ).
reverse( [H|T], SoFar, Answer ) :-
reverse( T, [H|SoFar], Answer ).
?- reverse( List, [], Answer ).
(This is an example of tail-recursive optimisation.)
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 14
Building recursive structures (3)
• Example run of reverse/3:
?- reverse( [a,b,c], [], Ans ).
Call: reverse( [a,b,c], [], Ans ).
Unify: [a,b,c] = [H|T], SoFar = []
Call: reverse( [b,c], [a], Ans ).
Unify: [b,c] = [H’|T’], SoFar’ = [a]
Call: reverse( [c], [b,a], Ans ).
Unify: [c] = [H’’|T’’], SoFar’’ = [b,a]
Call: reverse( [], [c,b,a], Ans ).
Unify: Ans = Answer, Answer = [c,b,a]
“Recursion”, CIS335: Logic Programming,
Goldsmiths’ College, London 15