Recursion and Iteration

Some Vocabulary and Background

CS 3540
Programming Languages and Paradigms

Recursive Programs

the world's largest lake on an island in a lake on an island
Crater Lake,
the Philippine Islands
[optional note]

Recursion is a technique for writing programs. Even when we think we know a word, checking out a dictionary definition can help us to understand it better. You can check out the definition of recursion at Merriam-Webster Online, which dates the first known use of "recursion" to 1790. Here is that definition, edited lightly for our purposes:

re · cur · sion
  1. RETURN sense 1: to go back or come back again
  2. the determination of a succession of elements (such as numbers or functions) by operation on one or more preceding elements according to a rule or formula involving a finite number of steps
  3. a computer programming technique involving the use of ... a function or algorithm that calls itself ...

To recur is to go back, in thought or discourse. A function can do that.

In computer science, a recursive program is one that:

As a result of the second part of this definition, we can see that a recursive program is defined, in part, in terms of itself. In practice, we create a function that calls itself.

We sometimes use recursive relationships to understand mathematical properties. For example, we can examine a sequence of values containing a number raised to a power and see a pattern:

    power(x, 0) = 1
    power(x, 1) = 1 * x         = x * power(x, 0)
    power(x, 2) = 1 * x * x     = x * power(x, 1)
    power(x, 3) = 1 * x * x * x = x * power(x, 2)

We can turn this into something more usable by turning the power itself into a variable and using an inductive definition to make the pattern explicit:

    power(x, 0) = 1
    power(x, n) = x * power(x, n-1)

In fact, there are programming languages -- such as Prolog and Haskell -- in which you write the recursive equations just like that!

In Racket, we would write:

    (define power       ; behaves like the built-in function expt
       (lambda (x n)
          (if (zero? n)
              (* x (power n (sub1 n))))))

The fundamental idea behind recursion is this: If a problem can be defined in terms of a similar, yet simpler problem, recursion may be a useful tool for expressing a solution.

Iteration and Recursion

"O, thou hast damnable iteration and
are indeed able to corrupt a saint."

-- Falstaff
, in Shakespeare's Henry IV

Most programmers learn to write loops first and then see recursion as a more difficult way to do things they can already do. But I think you'll find iteration and recursion are really pretty similar. More importantly, you think about the same things no matter which one you are using.

Consider the task described in the song "99 Bottles of Beer on the Wall".

How might we solve this task with a loop?

    n = 99;
    while (n > 0) {

Notice that you get to solve "n-1 bottles" with the same code that solved "n bottles". That's exactly what we do when we write a recursive solution:

    bottles_of_beer(0) = sing_last_verse()
    bottles_of_beer(n) = take_one_down()

In both cases, you have to answer the same questions:

Iteration and recursion really are the same kind of process. You may even have noticed that the dictionary definition of "recursion" we saw earlier says as much. The third entry of that definition ends with the phrase compare iteration!

Be not afraid. If you can write a loop, then you, too, can write recursive functions.

(See this footnote for the source of this example and for a little unexpected goodness.)

The Structure of Recursive Programs

More formally, we will say that every recursive program consists of:

The base cases are often pretty straightforward, but we still have to think. They encode our understanding of the problem by recording the answer to the smallest problem(s) in the domain.

Each recursive case consists of three steps:

  1. Split the data into smaller pieces.
  2. Solve the pieces.
  3. Combine the solutions for the parts into a single solution for the original argument.

This is usually where the descriptions of recursion end in our textbooks. "Okay," you might say, "great. But how do I do that?" The goal of the next few weeks is to help you feel this TL;DR in your bones:

Recursion doesn't have to be scary.   Sometimes, it's all about the data.

In particular, there are rules that help us to think about each of these steps:

  1. We split the data into sub-problems based on the type of the argument our function receives. For example: The structure of the data type matters (more soon!), so the data type matters.

  2. We will often define our data so that the "big" sub-problem is topologically similar to the original problem. For example, the rest of a list is also a list. The number n-1 is a also a number that can be decomposed. We will take advantage of this similarity by solving the "big" sub-problem with a recursive call.

  3. We combine the solutions for the sub-parts based on the type of value returned by the function. For example: There will be exceptions, especially in the case of lists. A list can be re-assembled with cons, but sometimes we need to use other list-producing functions such as list or append. A number can be assembled using max or some other binary operator. A boolean can be assembled using or or some other boolean function.

The driver for this process is the the type of the arguments that our function processes. This is where inductive specifications can help.

Note on Iteration and Recursion

I adopted my "99 Bottles of Beer on the Wall" example from this message on an Erlang mailing list. (Erlang is a cool programming language: not quite functional, not quite imperative. It handles concurrency as a primitive!)

That message continues with a bit of news that surprises many programmers who grew up with iteration before learning recursion: Iteration is a special case of recursion -- with limits.

Take a look at our examples above. In the loop, we always handle the one bottle first, then the remaining n-1 bottles. But in the recursive function, we could make the recursive call first to handle the n-1 bottles first, and then handle the one bottle last. We could even make the call between "take one down" and "pass it around", and interleave the activities! Try that with a loop.

In both iteration and recursion, a program handles an arbitrary number of steps with a single piece of code. This code either branches to the top of a loop (an implicit jump) or makes a function call (an explicit jump) -- and re-enters itself, so that one piece of code handles any number of things. In both cases, you have to make sure that some measure of the problem size (the length of a list, the number of things left to work on, the size of a tree, etc.) is strictly smaller every time you enter it.

But the function call is more powerful. As the creator of Scheme wrote back in the 1970s, lambda is the ultimate goto.

Note on Crater Lake

Take a close look at this image. The big island is Luzon, one of the Philippine Islands. In the middle of Luzon is Lake Taal. Inside Lake Taal is Vulcano Island. Notice Crater Lake, the dot of water in the middle of Vulcano Island. Crater Lake holds a unique distinction. It is the largest lake on an island in a lake on an island in the world.

How's that for recursion?

This is a bit more complicated than the simplest recursion. We have a lake on an island in a lake on an island. Two different kinds of objects are recursing back and forth between one another. As we learn next session, this is a special kind of recursion, worthy of its own programming technique!

You can see this image along with a few other fun lake/island combinations at The Island and Lake Combination.

Eugene Wallingford ..... ..... February 9, 2023