In functional languages like Lispex, iteration and looping are typically achieved through recursion. This guide will teach you what recursion is and how to write your own recursive functions to solve common problems.
What is Recursion?
Recursion is a simple but powerful idea: a function that calls itself.
To prevent a function from calling itself forever, a recursive function must have two key parts:
- Base Case: A condition that tells the function when to stop recursing.
- Recursive Step: The part where the function calls itself, but with a slightly different input that moves it closer to the base case.
Let's see this in action with a classic example.
Example 1: Calculating a Factorial
The factorial of a number n (written as n!) is the product of all positive integers up to n. For example, 5! = 5 * 4 * 3 * 2 * 1 = 120.
Here is a recursive function in Lispex to calculate a factorial:
(define (factorial n)
(if (<= n 1)
1
(* n (factorial (- n 1)))))
(factorial 5) ; ⇒ 120
How It Works
When we call (factorial 3), here's what the interpreter does:
- Is
3 <= 1? No. So, calculate(* 3 (factorial 2)). - To do that, it must calculate
(factorial 2). Is2 <= 1? No. So, calculate(* 2 (factorial 1)). - To do that, it must calculate
(factorial 1). Is1 <= 1? Yes. It hits the base case and returns1. - Now it can solve step 2:
(* 2 1)which is2. - Now it can solve step 1:
(* 3 2)which is6.
The final result is 6.
Example 2: Recursion on a List
Recursion is the primary way to "loop" over a list in Lispex. The common pattern is to process the first element and then recurse on the rest of the list.
Let's write a function to sum all the numbers in a list.
(define (sum-list lst)
(if (empty? lst)
0
(+ (list-first lst) (sum-list (list-rest lst)))))
(sum-list (list 10 20 30)) ; ⇒ 60
How It Works
sum-listis called with(list 10 20 30).- The list is not empty, so it calculates
(+ 10 (sum-list (list 20 30))). sum-listis called with(list 20 30).- The list is not empty, so it calculates
(+ 20 (sum-list (list 30))). sum-listis called with(list 30).- The list is not empty, so it calculates
(+ 30 (sum-list (list))). sum-listis called with an empty list(). It hits the base case and returns0.- The results then "bubble up":
(+ 30 0)is30,(+ 20 30)is50, and finally(+ 10 50)is60.
A Note on Tail Recursion
For very deep recursion (e.g., (factorial 10000)), the simple recursive pattern can sometimes lead to an error called a "stack overflow." To solve this, you can use a technique called tail recursion.
A function is tail-recursive if the recursive call is the very last thing it does. This allows the Lispex interpreter to perform an optimization (Tail Call Optimization) to avoid stack overflow errors. This is often achieved using an "accumulator" variable.
Here's the tail-recursive version of our factorial function:
(define (factorial n)
(let ((factorial-helper (lambda (n acc)
(if (<= n 1)
acc
(factorial-helper (- n 1) (* acc n))))))
(factorial-helper n 1)))
(factorial 5) ; ⇒ 120 (tail-recursive)
Conclusion
Recursion is a fundamental concept in Lispex. By understanding the base case and recursive step, you can perform any kind of iteration or looping in a clean, functional way.
Now, try writing your own recursive function! For example, can you write a function called my-length that calculates the length of a list recursively?