Recursion with Accumulators
Introduction to the Accumulator Pattern
In our previous lesson, we learned how to use plain recursion to process lists item by item. With plain recursion, a function calls itself until it reaches a base case, and then it builds up the final result step by step on the way back up.
However, in many programs, it is helpful to keep track of a "running result" as we move forward through the data. If you have ever written a loop in another programming language where you updated a variable at each step, you already know why this is useful. In Haskell, we do not have loops or variables that we can update. Instead, we use a concept called an accumulator.
An accumulator is simply an extra parameter that we pass to our recursive function. It carries our running total (or running structure) through each step of the recursion.
To use an accumulator cleanly, we follow a standard Haskell pattern:
- We write a top-level wrapper function to provide a "starting seed" (like starting a total at
0). - We write a helper function inside a
whereblock (traditionally namedgo) that does the actual recursive work and updates theaccumulator.
This beginner version demonstrates the accumulator shape. For very large lists, Haskell programmers often use strict accumulation to avoid building delayed work, and we will discuss that later.
Let's look at how we can build functions using this pattern.
Example 1: Summing a List (sumList)
We will start by rewriting a function to sum a list of integers, but this time we will use an accumulator.
First, we define our top-level function. It takes a list of integers and returns an integer. Inside, it immediately calls a helper function named go, passing 0 as the starting value of our running total, along with the list xs.
Next, we need to define go. We use a where block to attach go directly to sumList so that it stays neatly hidden as an internal helper. go will take two parameters: our accumulator (an Int) and our list (a [Int]).
Now, let's add our base case to go. When the list is empty [], it means we have finished looking at all the numbers. Unlike plain recursion where an empty list might return 0, here our final answer is already stored in our accumulator. So, we simply return the accumulator acc.
Finally, we write the recursive case. When the list has a head y and a tail ys, we call go again. This time, we update the accumulator by adding the current item y to it (acc + y), and we pass the rest of the list ys to continue the process.
Here is the complete code, including a main function to print the result:
When you run this code, it will output:
Notice how 0 becomes 1, then 3, then 6, and finally 10 as the accumulator is passed forward through each recursive call.
