`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
