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