liveBook · Manning

Lazy-seq macro

As explained in Clojure: The Essential Reference

lazy-seq is a Clojure macro that constructs a sequence whose body is evaluated and cached on demand.

`lazy-seq` creates a sequence whose body runs only when its contents are requested. It caches the result, so accessing the same lazy sequence again does not rerun that body. Use it when writing a sequence producer that should do work on demand. section 9.3.1

Contract and edge cases:

  • `(lazy-seq & body)` accepts any number of expressions, evaluated as an implicit `do`. The last expression must yield something sequential; `(lazy-seq 1 2 [3])` yields `(3)`. section 9.3.1
  • A nonsequential final value raises `IllegalArgumentException`. With no arguments or an empty collection, the result is an empty sequence, not `nil`. section 9.3.1

Evaluation happens on first access

`(def output (lazy-seq (println "evaluated") '(1 2 3)))` prints nothing. The first `(first output)` prints `evaluated` and returns `1`; a second `(first output)` returns `1` without printing. section 9.3.1

For element-by-element laziness, put `lazy-seq` around each recursive step and use `cons` to attach the next value. Merely wrapping an already computed collection cannot delay the work that computed it. In the recursive mapping example, the eager version prints `iteration` for every input during construction; the lazy version prints it when its first element is requested. section 9.3.1

(defn eager-map [f coll] ; #1
  (when-first [x coll]
    (println "iteration")
    (cons (f x)
          (eager-map f (rest coll)))))

(def eager-out (eager-map str '(0 1 2))) ; #2
;; iteration
;; iteration
;; iteration

(defn lazy-map [f coll] ; #3
  (lazy-seq
    (when-first [x coll]
      (println "iteration")
      (cons (f x)
            (lazy-map f (rest coll))))))

(def lazy-out (lazy-map str '(0 1 2))) ; #4
(first lazy-out) ; #5
;; "iteration"
;; "0"

Compare where `lazy-seq` sits relative to the recursive call, then compare when each version prints `iteration`. section 9.3.1

Macro expansion shows the mechanism: `lazy-seq` constructs a `clojure.lang.LazySeq` with a function holding the body. Its caching resembles memoization, but the sequence has a predetermined order of values rather than function arguments used as cache keys. Like other cached sequences, retaining realized values can use memory. Nested unrealized steps can also cause stack overflow when traversed; laziness alone does not make every recursive producer safe. section 9.3.1 section 2.4.2

How Clojure: The Essential Reference covers it

How deep it goes
Defines the body contract and output, demonstrates caching and recursive construction, then shows macro expansion and stack-overflow pitfalls.
Examples
Uses printed evaluation traces, eager and lazy mapping, prime generation, and nested sequences that overflow the stack.
What it assumes
Assumes familiarity with Clojure functions and sequences while defining lazy-seq’s argument and return contract.
What you can do after
Build it from scratch: write a recursive lazy sequence producer with cons and recognize evaluation, caching, and traversal hazards.
How it connects
Connects lazy-seq to cons, recursive sequence generation, memoization, seq, and sequence.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Clojure: The Essential Reference. The section links point to the parts of the book it draws on.