liveBook · Manning

Lazy list

As explained in Clojure: The Essential Reference

A lazy sequence is a sequence whose elements are computed incrementally as they are requested.

A lazy list, or lazy sequence, produces elements as a consumer requests them rather than computing the entire result up front. This makes large and even infinite sequences useful when only a finite prefix is needed. Defining `(range 1e20)` returns immediately; asking for elements starts the work. section 3.4.2

The main contract is about when work happens and how much is requested:

  • Generators such as `range`, `iterate`, and `repeatedly` can supply elements incrementally. Bound an infinite result with a consumer such as `take`. section 3.4.2 section 9.2.1
  • `map` transforms elements; `filter` and `remove` select them. Their collection-taking forms return lazy sequences, so avoid relying on exactly-once side effects in mapping functions or predicates. section 3.5.2 section 3.5.3
  • An empty result is possible. `(seq [])` returns `nil`, whereas `(sequence [])` returns an empty sequence; `lazy-seq` itself does not return `nil`. section 9.1.1 section 9.3.1

Transform only what is needed

A pipeline can describe an unbounded computation while requesting a small result: `(take 5 (filter odd? (map inc (range))))` `;; (1 3 5 7 9)` `range` supplies numbers, `map` increments them, and `filter` retains odd results. `take` limits the requested output; no complete infinite result is required. section 3.4.2 section 3.5.2 section 3.5.3

To write a lazy producer, put the recursive step inside `lazy-seq` and join each produced value to the remainder with `cons`. The wrapper delays evaluation and caches its result once realized. Its body must ultimately yield something sequential: `(lazy-seq 1 2 3)` throws an `IllegalArgumentException` because its last expression is not a sequence. 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 the eager and lazy versions of `map`: constructing the eager result prints every iteration, while the lazy version waits until `first` requests an element. section 9.3.1

Laziness does not make full traversal free. Counting a general lazy sequence walks and realizes it, usually taking linear time; a counted `long-range` is a notable exception. Holding on to a sequence’s head while consuming its tail can also retain realized elements and exhaust memory. Deeply nested lazy steps can overflow the stack. section 8.1.2 section 3.4.2 section 9.3.1

Incremental output does not guarantee an incremental input. `re-seq` can return just the first few matches, but its source string must already be in memory. Likewise, a lazy database result must be consumed before its connection closes, and streaming benefits depend on the driver. Use laziness to limit requested work, then check what the producer retains or requires. section 9.3.5 section 9.3.7

How Clojure: The Essential Reference covers it

How deep it goes
Explains delayed evaluation, caching, recursive construction, realization costs, head retention, and stack-overflow cases.
Examples
Uses infinite ranges, eager-versus-lazy mapping, prime generation, regular-expression matches, and JDBC rows.
What it assumes
Assumes familiarity with Clojure functions and sequential collections while explaining lazy construction from `lazy-seq` and `cons`.
What you can do after
Build it from scratch with `lazy-seq` and `cons`, and diagnose when consumption retains memory or outlives a resource.
How it connects
Connects lazy sequences to `map`, `filter`, `cons`, `count`, transducers, and streaming database results.

Also explained in

Compare how 2 books explain lazy list →

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.