1. Donate
  2. Resume
  3. Blog
  4. Projects
  5. About

Clojure's forgotten for loop

Higher order functions in Clojure get a great deal of attention, and for good reason. Clojure has a rich standard library of functions which focus on purely transforming data. To those studying Clojure, the for macro for list comprehension may stand out as verbose and awkward; it may also go entirely unnoticed.

List comprehension

Many popular languages these days have list comprehension, more commonly dynamic ones, and some are even rather imperative, like Python. Here are some examples of how for can be used in Clojure.

Map

(for [x (range 10 15)]
  (str "|" x "|"))

; => ("|10|" "|11|" "|12|" "|13|" "|14|")

Filter

The :when modifier allows filtering, based on a predicate. Iteration won’t be stopped, but any iteration which doesn’t yield truthy from the predicate will be skipped. In contrast, the :while modifier allows early termination, based on a predicate. The :while predicate can only return false once, since for will stop iterating immediately and return the accumulated result.

(for [x {:a 1 "b" 2 :c 3}
      :when (-> x first keyword?)]
  x)

; => ([:a 1] [:c 3])

(for [x (range 3)
      y (range 3)
      :when (not= x y)]
  [x y])

; => ([0 1] [0 2] [1 0] [1 2] [2 0] [2 1])

(for [x (range 3)
      y (range 3)
      :while (not= x y)]
  [x y])

; => ([1 0] [2 0] [2 1])

Create intermediate bindings

It’s possible to create bindings per-iteration; they have access to all bindings above them.

(for [i (range 1 10)
      :when (even? i)
      :let [inverse (/ 1 i)]]
  [i inverse])

; => ([2 1/2] [4 1/4] [6 1/6] [8 1/8])

Extract map values

It’s possible to destructure within the bindings of for, allowing for easy access to nested values.

(for [[k v] {:a 1 :b 2 :c 3}]
  v)

; => (1 2 3)

Nested iteration

Subsequent bindings in the for macro will cause nested iteration, each subsequent binding iterating more quickly than the former.

(for [c [:2 :3 :4 :5 :6 :7 :8 :9 :10 :J :Q :K :A]
      s [:♠ :♥ :♣ :♦]]
  [c s])

; => ([:2  :♠] [:2  :♥] [:2  :♣] [:2  :♦]
;     [:3  :♠] [:3  :♥] [:3  :♣] [:3  :♦]
;     [:4  :♠] [:4  :♥] [:4  :♣] [:4  :♦]
;     [:5  :♠] [:5  :♥] [:5  :♣] [:5  :♦]
;     [:6  :♠] [:6  :♥] [:6  :♣] [:6  :♦]
;     [:7  :♠] [:7  :♥] [:7  :♣] [:7  :♦]
;     [:8  :♠] [:8  :♥] [:8  :♣] [:8  :♦]
;     [:9  :♠] [:9  :♥] [:9  :♣] [:9  :♦]
;     [:10 :♠] [:10 :♥] [:10 :♣] [:10 :♦]
;     [:J  :♠] [:J  :♥] [:J  :♣] [:J  :♦]
;     [:Q  :♠] [:Q  :♥] [:Q  :♣] [:Q  :♦]
;     [:K  :♠] [:K  :♥] [:K  :♣] [:K  :♦]
;     [:A  :♠] [:A  :♥] [:A  :♣] [:A  :♦])

Pairwise disjoint sets

The nested looping can be used to flatten nested sequences.

(defn pairwise-disjoint? [s]
  (->> (for [s' s
             r s']
         r)
       (apply distinct?)))

(pairwise-disjoint? #{#{:a :b :c :d :e}
                      #{:a :b :c :d}
                      #{:a :b :c}
                      #{:a :b}
                      #{:a}})

; => false

Worth noting

Those coming from the imperative camp may look to for to achieve side-effects. That won’t work well, since Clojure’s for is lazy; if it’s not consumed, it’ll never be realized. It may also only be partially consumed. Instead, consider doseq.

Most of the time, using map or filter will be not only more clear, but also more concise. If you want early termination, however, or nested iterations, it’s worthwhile to know the semantics of Clojure’s for.


Related posts