This guide covers:

• What are lazy sequences
• Pitfalls with lazy sequences
• How to create functions that produce lazy sequences
• How to force evaluation

What Version of Clojure Does This Guide Cover?

This guide covers Clojure 1.5.

Overview

Clojure is not a lazy language.

However, Clojure supports lazily evaluated sequences. This means that sequence elements are not available ahead of time and produced as the result of a computation. The computation is performed as needed. Evaluation of lazy sequences is known as realization.

Lazy sequences can be infinite (e.g. the sequence of Fibonacci numbers, a sequence of dates with a particular interval between them, and so on). If a lazy sequence is finite, when its computation is completed, it becomes fully realized.

When it is necessary to fully realize a lazy sequence, Clojure provides a way to force evaluation (force realization).

Benefits of Lazy Sequences

Lazy sequences have two main benefits:

• They can be infinite
• Full realization of interim results can be avoided

Producing Lazy Sequences

Lazy sequences are produced by functions. Such functions either use the clojure.core/lazy-seq macro or other functions that produce lazy sequences.

clojure.core/lazy-seq accepts one or more forms that produce a sequence of nil (when the sequence is fully realized) and returns a seqable data structure that invokes the body the first time the value is needed and then caches the result.

For example, the following function produces a lazy sequence of random UUIDs strings:

(defn uuid-seq
[]
(lazy-seq
(cons (str (random-uuid))
(uuid-seq))))

Note: the random-uuid function is available in ClojureScript and was introduced into Clojure in version 1.11 Alpha 3. Prior to that, you needed to use Java interop:

(defn uuid-seq
[]
(lazy-seq
(cons (str (java.util.UUID/randomUUID))
(uuid-seq))))

Another example:

(defn fib-seq
"Returns a lazy sequence of Fibonacci numbers"
([]
(fib-seq 0 1))
([a b]
(lazy-seq
(cons b (fib-seq b (+ a b))))))

Both examples use clojure.core/cons which prepends an element to a sequence. The sequence can in turn be lazy, which both of the examples rely on.

Even though both of these sequences are infinite, taking first N elements from each does return successfully:

(take 3 (uuid-seq))
(take 10 (fib-seq))
(take 20 (fib-seq))

Realizing Lazy Sequences (Forcing Evaluation)

Lazy sequences can be forcefully realized with clojure.core/dorun and clojure.core/doall. The difference between the two is that dorun throws away all results and is supposed to be used for side effects, while doall returns computed values:

(dorun (map inc [1 2 3 4]))
(doall (map inc [1 2 3 4]))

Commonly Used Functions That Produce Lazy Sequences

Multiple frequently used clojure.core functions return lazy sequences, most notably:

• map
• filter
• remove
• range
• take
• take-while
• drop
• drop-while

The following example uses several of these functions to return 10 first even numbers in the range of [0, n):

(take 10 (filter even? (range 0 100)))

Several functions in clojure.core are designed to produce lazy sequences:

• repeat
• iterate
• cycle

For example:

(take 3 (repeat "ha"))
(take 5 (repeat "ha"))
(take 3 (cycle [1 2 3 4 5]))
(take 10 (cycle [1 2 3 4 5]))
(take 3 (iterate (partial + 1) 1))
(take 5 (iterate (partial + 1) 1))

Lazy Sequences Chunking

There are two fundamental strategies for implementing lazy sequences:

• Realize elements one-by-one
• Realize elements in groups (chunks, batches)

In Clojure 1.1+, lazy sequences are chunked (realized in chunks).

For example, in the following code

(take 10 (range 1 1000000000000))

one-by-one realization would realize one element 10 times. With chunked sequences, elements are realized ahead of time in chunks (32 elements at a time).

This reduces the number of realizations and, for many common workloads, improves efficiency of lazy sequences.

Contributors

Michael Klishin michael@defprotocol.org, 2013 (original author)