Here’s a way to avoid recursive helper functions…
By using loop in Clojure.
Sometimes recursive helper functions seem needed, like fibonacci-iter:
(defn- fibonacci-iter [a b count]
(if (= count 0)
b
(recur (+ a b) a (- count 1))))
(defn fibonacci [n]
(fibonacci-iter 1 0 n))
Code language: Clojure (clojure)
(I ported this to Clojure from Structure and Interpretation of Computer Programs, licensed CC BY-SA-4.0)
That fibonacci-iter helper function is only needed for recursion.
For example, the public (defn fibonacci [n] only needs an argument n for the index of the Fibonacci number.
(fibonacci 1) returns 1
(fibonacci 2) returns 1
(fibonacci 3) returns 2
(fibonacci 4) returns 3
(fibonacci 5) returns 5
etc…
But fibonacci-iter needs arguments to keep track of the recursion.
(defn- fibonacci-iter [a b count]
Arguments in each recursion
| a | b | count |
|---|---|---|
| 1 | 0 | 5 |
| 1 | 1 | 4 |
| 2 | 1 | 3 |
| 3 | 2 | 2 |
| 5 | 3 | 1 |
| 8 | 5 (returns this, ending recursion) | 0 |
These arguments tell it when to end the recursion, and what to return:
(defn- fibonacci-iter [a b count]
(if (= count 0)
b
(recur (+ a b) a (- count 1))))
Code language: Clojure (clojure)
With loop in Clojure, you don’t need fibonacci-iter:
(defn fibonacci [n]
(loop [a 1
b 0
count n]
(if (= count 0)
b
(recur (+ a b) a (- count 1)))))
Code language: Clojure (clojure)
loop is like inlining fibonacci-iter, but it lets you set an initial value for the arguments:
(defn fibonacci [n]
(loop [a 1 ;; a has an initial value of 1
b 0 ;; b has an initial value of 0
count n] ;; count has an initial value of n
(if (= count 0)
b
(recur (+ a b) a (- count 1)))))
Code language: Clojure (clojure)
And once this calls recur on line 7, those initial values don’t apply anymore.
It sets a, b, and count to whatever it passes to recur:
(defn fibonacci [n]
(loop [a 1 ;; a is the 1st argument of recur on line 7
b 0 ;; b is the 2nd argument of recur on line 7
count n] ;; count is the 3rd argument of recur on line 7
(if (= count 0)
b
(recur (+ a b) a (- count 1))))) ;; goes back to line 2
Code language: Clojure (clojure)
This is a simple example.
But it helps more when the function is bigger.
Here’s an example function where loop helps more.
This function, expire-n, removes expired keys from a data store:
Helper Function
;; Recursive helper function
(defn- iter-keys-expired [time n store expired-store]
(if
(or (<= (count expired-store) (* 0.25 n)) (= (count expired-store) (count store)))
expired-store
(recur n time store
(union
expired-store
(->> expired-store
(apply dissoc store)
has-exp
(take n)
(expired time))))))
(defn expire-n [time n store]
(let [keys-expired
(iter-keys-expired time n store (->> store
has-exp
(take n)
(expired time)))]
(apply dissoc store keys-expired)))
Code language: Clojure (clojure)
Loop, No Helper Function
(defn expire-n [store time n]
(let [keys-expired
;; loop replaces recursive helper function
(loop [expired-store (->> store
has-exp
(take n)
(expired time))]
(if
(or (<= (count expired-store) (* 0.25 n)) (= (count expired-store) (count store)))
expired-store
(recur (union
expired-store
(->> expired-store
(apply dissoc store)
has-exp
(take n)
(expired time))))))]
(apply dissoc store keys-expired)))
Code language: Clojure (clojure)
The loop Clojure form is more useful in expire-n than in fibonacci, because expire-n is more complex.
Calling iter-keys-expired makes you look above expire-n to see what it does.
Loop makes recursion easier to understand.

Leave a Reply