Skip to content
Lazy Sequences
step 1/5

Reading — step 1 of 5

Learn

~1 min readSTM and Lazy Sequences

Most Clojure sequence operations are lazy — they don't compute until consumed. This lets you work with infinite sequences and avoid unnecessary work.

(def naturals (iterate inc 1))    ;; (1 2 3 4 5 ...) — infinite!

(take 5 naturals)                  ;; (1 2 3 4 5)
(take 5 (map #(* % %) naturals))  ;; (1 4 9 16 25)
(first (drop 1000000 naturals))   ;; 1000001 — only computes 1M+1 elements

Generators with lazy-seq:

(defn primes
    ([] (primes 2))
    ([n] (lazy-seq
             (cons n (filter #(not= 0 (mod % n)) (primes (inc n)))))))

(take 10 (primes))   ;; first 10 primes

The lazy-seq macro defers the body's evaluation until the sequence is consumed. The recursive call doesn't blow the stack — each step is computed on demand.

Realization — when does laziness end?

  • doall — force the whole seq
  • dorun — like doall but discards results (for side effects)
  • take, drop, nth, count — partial realization
  • first, rest, next — peek without realizing more than needed

Pitfalls:

  • Holding the head: keeping a reference to a lazy seq's first element prevents GC of the rest. Memory leak.
  • Infinite seqs in count — never returns.
  • Side effects in map body — only runs when consumed. Use doseq or mapv for eager.

iterate, repeatedly, range, cycle — common lazy generators:

(take 5 (iterate #(* 2 %) 1))     ;; (1 2 4 8 16)
(take 3 (repeatedly #(rand)))     ;; 3 random numbers
(range 1 10 2)                     ;; (1 3 5 7 9)
(take 6 (cycle [:a :b :c]))        ;; (:a :b :c :a :b :c)

Streams over big files:

(with-open [r (clojure.java.io/reader "big.log")]
    (->> (line-seq r)
         (filter #(re-find #"ERROR" %))
         (take 100)
         (doall)))    ;; force realization while reader still open

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…