Basic Idea Of Recursion
recursive: defined in terms of itself
Simple, right?! It means for a predicate to qualify as recursive, it must
include itself as a sub-goal. Thus, the simplest, and most useless, recursive
predicate one could write would be: warm_up_cpu :- warm_up_cpu.
So, in
practical terms, there’s a little more to writing recursive predicates than
just that.
Exiting Recursion
For nearly all recursive predicates, we want them to end their computation at
some point. This means we need an exit clause. As Prolog checks from top to
bottom, typically we put the exit clause for checking first. Then we’ll put
some predicate to do what we want. The simplest use-case here is in getting
user input; this looping-until-valid recursion is a great way to validate user
input. In this example, we only want to read once, so we use the ;
operator
for disjunction within a single clause.
Don’t forget to terminate your input with a full stop!
?-
So, in this example, we read some user input, which for our imaginary
application needs to be a number. Our exit goal is number/1
; if this
validates, then we !
cut to remove the following choice-point left by ;
read_number(N)
, as we only need their first valid answer. Our recursive call
is only made if number/1
fails. So when you type in "pickles.", Prolog
checks number(pickles)
, which will fail. But Prolog sees the ;
disjunction
and checks if that other side will succeed.
That’s where Prolog finds the recursive call back to read_number(N)
, and in
trying to prove that, it will once again ask you for input with read/1
,
which it tries to validate again with number/1
. This repeats until you input
some number such as "42."
Towards A Goal
Typically, when doing recursion, we’re moving towards some goal, so our recursive call should accomplish this task. In this example, we also combine the exit clause with the recursive call in the same body. In this case, the exit clause is when the number is 0, but the recursive call needs to be guarded from numbers less than 0 to prevent infinite recursion. The guard (a condition that stops it) exits the recursion.
Ask for the next answer until you get to false to see the recursion in play:
?-
When we call countdown(5, CD)
, Prolog first checks if 5 > 0
, our exit
clause; it succeeds, so next sub-goal. Now Prolog checks if Less1 is 5 - 1
and decides that Less1
unifies with 4
, so next sub-goal. This next
sub-goal is disjunctive—only one side of the ;
operator needs to succeed for
countdown/2
to succeed. Prolog begins with the left-hand side and tries to
unify CD
with 4
, which it can do. At this point, Prolog tells us
countdown(5, 4)
succeeds and that there’s a choice-point left, so we can
backtrack.
This choice-point is the right-hand side of the ;
operator, so if we ask
Prolog for another solution, it finds itself trying to prove countdown(4,
CD)
, as Less1
is unified to 4
at this point. When countdown(4, CD)
has
been proven to succeed if CD
unifies with 3
, as this CD
is the same CD
as in the head of the clause we’re trying to prove (countdown(5, CD)
),
Prolog will tell us countdown(5, 3)
also succeeds. This continues until
Prolog is trying to prove countdown(0, CD)
; when it tests if 0 > 0
, this
fails, there are no further choice-points, so Prolog fails the query.
Advice
The best thing you can do to understand recursion, if you’re struggling with
tracing how it executes, is to grab a pencil and paper and write out the goals
for a small example like countdown(3, N)
. If you’ve not got your head around
unification yet, though, do that first!
As ever, practise makes perfect. You could try writing a predicate to read user input that must be one of rock, paper, or scissors. Then try a square-root approximation predicate—here’s a YouTube video describing the algorithm. If you want a fun project, you’ll find a couple of places for recursion in the classic “higher/lower” guess-a-number game: accepting valid user input and guessing again.
Until next time, Happy Prologing!
Paul