Haskell/Recursion

Recursive functions play a central role in Haskell, and are used throughout computer science and mathematics generally. Recursion is basically a form of repetition, and we can understand it by making distinct what it means for a function to be recursive, as compared to how it behaves.

A recursive function simply means this: a function that has the ability to invoke itself.

And it behaves such that it invokes itself only when a condition is met, as with an if/else/then expression, or a pattern match which contains at least one base case that terminates the recursion, as well as a recursive case which causes the function to call itself, creating a loop.

Without a terminating condition, a recursive function may remain in a loop forever, causing an infinite regress.

The factorial function
Mathematics (specifically combinatorics) has a function called factorial. It takes a single non-negative integer as an argument, finds all the positive integers less than or equal to “n”, and multiplies them all together. For example, the factorial of 6 (denoted as $$6!$$) is $$1 \times 2 \times 3 \times 4 \times 5 \times 6 = 720$$. We can use a recursive style to define this in Haskell:

Let's look at the factorials of two adjacent numbers:

Notice how we've lined things up. You can see here that the $$6!$$ includes the $$5!$$. In fact, $$6!$$ is just $$6 \times 5!$$. Let's continue:

The factorial of any number is just that number multiplied by the factorial of the number one less than it. There's one exception: if we ask for the factorial of 0, we don't want to multiply 0 by the factorial of -1 (factorial is only for positive numbers). In fact, we just say the factorial of 0 is 1 (we define it to be so. Just take our word for it that this is right. ). So, 0 is the base case for the recursion: when we get to 0 we can immediately say that the answer is 1, no recursion needed. We can summarize the definition of the factorial function as follows:


 * The factorial of 0 is 1.
 * The factorial of any other number is that number multiplied by the factorial of the number one less than it.

We can translate this directly into Haskell:

This defines a new function called. The first line says that the factorial of 0 is 1, and the second line says that the factorial of any other number  is equal to   times the factorial of. Note the parentheses around the ; without them this would have been parsed as  ; remember that function application (applying a function to a value) takes precedence over anything else when grouping isn't specified otherwise (we say that function application binds more tightly than anything else).

The example above demonstrates the simple relationship between factorial of a number, n, and the factorial of a slightly smaller number, n - 1.

Think of a function call as delegation. The instructions for a recursive function delegate a sub-task. It just so happens that the delegate function uses the same instructions as the delegator; it's only the input data that changes. The only really confusing thing about recursive functions is the fact that each function call uses the same parameter names, so it can be tricky to keep track of the many delegations.

Let's look at what happens when you execute :


 * 3 isn't 0, so we calculate the factorial of 2
 * 2 isn't 0, so we calculate the factorial of 1
 * 1 isn't 0, so we calculate the factorial of 0
 * 0 is 0, so we return 1.
 * To complete the calculation for factorial 1, we multiply the current number, 1, by the factorial of 0, which is 1, obtaining 1 (1 × 1).
 * To complete the calculation for factorial 2, we multiply the current number, 2, by the factorial of 1, which is 1, obtaining 2 (2 × 1 × 1).
 * To complete the calculation for factorial 3, we multiply the current number, 3, by the factorial of 2, which is 2, obtaining 6 (3 × 2 × 1 × 1).

(Note that we end up with the one appearing twice, since the base case is 0 rather than 1; but that's okay since multiplying by 1 has no effect. We could have designed  to stop at 1 if we had wanted to, but the convention (which is often useful) is to define the factorial of 0.)

When reading or composing recursive functions, you'll rarely need to “unwind” the recursion bit by bit — we leave that to the compiler.

One more note about our recursive definition of : the order of the two declarations (one for   and one for  ) is important. Haskell decides which function definition to use by starting at the top and picking the first one that matches. If we had the general case before the 'base case', then the general   would match anything passed into it – including 0. The compiler would then conclude that  equals , and so on to negative infinity (clearly not what we want). So, always list multiple function definitions starting with the most specific and proceeding to the most general.

Loops, recursion, and accumulating parameters
Imperative languages use loops in the same sorts of contexts where Haskell programs use recursion. For example, an idiomatic way of writing a factorial function in C, a typical imperative language, would be using a for loop, like this:

Here, the for loop causes  to be multiplied by   repeatedly. After each repetition,  is subtracted from   (that is what   does). The repetitions stop when  is no longer greater than.

A straightforward translation of such a function to Haskell is not possible, since changing the value of the variables  and   (a destructive update) would not be allowed. However, you can always translate a loop into an equivalent recursive form by making each loop variable into an argument of a recursive function. For example, here is a recursive “translation” of the above loop into Haskell:

is an auxiliary function which actually performs the factorial calculation. It takes an extra argument,, which is used as an accumulating parameter to build up the final result.

Other recursive functions
As it turns out, there is nothing particularly special about the  function; a great many numeric functions can be defined recursively in a natural way. For example, let's think about multiplication. When you were first learning multiplication (remember that moment? :)), it may have been through a process of 'repeated addition'. That is, 5 × 4 is the same as summing four copies of the number 5. Of course, summing four copies of 5 is the same as summing three copies, and then adding one more – that is, 5 × 4 = 5 × 3 + 5. This leads us to a natural recursive definition of multiplication:

Stepping back a bit, we can see how numeric recursion fits into the general recursive pattern. The base case for numeric recursion usually consists of one or more specific numbers (often 0 or 1) for which the answer can be immediately given. The recursive case computes the result by calling the function recursively with a smaller argument and using the result in some manner to produce the final answer. The 'smaller argument' used is often one less than the current argument, leading to recursion which 'walks down the number line' (like the examples of  and   above). However, the prototypical pattern is not the only possibility; the smaller argument could be produced in some other way as well.

List-based recursion
Haskell has many recursive functions, especially concerning lists. Consider the  function that finds the length of a list:

So, the type signature of  tells us that it takes any type of list and produces an. The next line says that the length of an empty list is 0 (this is the base case). The final line is the recursive case: if a list isn't empty, then it can be broken down into a first element (here called ) and the rest of the list (which will just be the empty list if there are no more elements) which will, by convention, be called   (i.e. plural of  ). The length of the list is 1 (accounting for the ) plus the length of   (as in the   example in Next steps,   is set when the argument list matches the  pattern).

Consider the concatenation function  which joins two lists together:

This is a little more complicated than. The type says that  takes two lists of the same type and produces another list of the same type. The base case says that concatenating the empty list with a list  is the same as   itself. Finally, the recursive case breaks the first list into its head and tail  and says that to concatenate the two lists, concatenate the tail of the first list with the second list, and then tack the head   on the front.

There's a pattern here: with list-based functions, the base case usually involves an empty list, and the recursive case involves passing the tail of the list to our function again, so that the list becomes progressively smaller.

Recursion is used to define nearly all functions to do with lists and numbers. The next time you need a list-based algorithm, start with a case for the empty list and a case for the non-empty list and see if your algorithm is recursive.

Don't get TOO excited about recursion...
Despite its ubiquity in Haskell, one rarely has to write functions that are explicitly recursive. Instead, standard library functions perform recursion for us in various ways. For example, a simpler way to implement the  function is:

Almost seems like cheating, doesn't it? :) This is the version of  that most experienced Haskell programmers would write, rather than the explicitly recursive version we started out with. Of course, the   function uses some list recursion behind the scenes, but writing   in this way means you, the programmer, don't have to worry about it.