Loop in Clojure

Circle Cafe front sign, Loop in Clojure

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

abcount
105
114
213
322
53 1
85 (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.

One response to “Loop in Clojure”

  1. Ryan Kienstra Avatar

    Loop is a recursive form, it’s not like a loop from imperative programming.

Leave a Reply

Your email address will not be published. Required fields are marked *