Clojure Guides - Language - Laziness
Clojure Guides - Language - Laziness
This work is licensed under a Creative Commons Attribution 3.0 Unported License
(https://creativecommons.org/licenses/by/3.0/) (including images & stylesheets). The source is available
on Github (https://github.com/clojure-doc/clojure-doc.github.io).
Overview
Clojure is not a lazy language (http://en.wikipedia.org/wiki/Lazy_evaluation).
However, Clojure supports lazily evaluated sequences. This means that sequence elements are not
available ahead of time and are 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).
clojure.core/lazy-seq accepts one or more forms that produce a sequence, or nil when the
sequence is fully realized, and returns a seqable data structure that evaluates 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))))
#'cljs.user/uuid-seq
Note: the random-uuid function was added to Clojure in version 1.11 but was previously available
in ClojureScript.
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))))))
#'cljs.user/fib-seq
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))
("a7f94078-47f7-409a-a741-913b6530b4ab"
"9e637e31-add4-4b78-af74-f66dc3ee6545"
"d03b7593-9158-43d0-9da8-e81207686603")
(take 10 (fib-seq))
(1 1 2 3 5 8 13 21 34 55)
(take 20 (fib-seq))
nil
(2 3 4 5)
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):
(0 2 4 6 8 10 12 14 16 18)
repeat
iterate
cycle
For example:
(1 2 3)
(1 2 3 4 5 1 2 3 4 5)
(1 2 3)
(1 2 3 4 5)
(1 2 3 4 5 6 7 8 9 10)
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).
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(1 2 3 4 5 6 7 8 9 10)
What you'll see here is that even tho' the result is a sequence of 10 elements, 32 elements are printed.
This is because realizing the first element causes the whole of the first chunk to be realized.
This typically reduces the number of realizations and, for many common workloads, improves efficiency of
lazy sequences.
Clojure on the JVM has optimized some functions, such as range , to produce individual elements
efficiently without chunking:
user=> (take 10 (map #(do (println %) (inc %)) (range)))
(0
1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10)
user=>
What you'll see here is that only 10 elements are printed, not 32, and they are printed one-by-one, as each
element of the sequence is realized.
If you used (range 100) instead of (range) in the above example, you'd see 32 elements printed, as
before.
Contributors
Michael Klishin [email protected] (mailto:[email protected]), 2013 (original author)
Links
About (/articles/about/)
Table of Contents (/articles/content/)
Getting Started (/articles/tutorials/getting_started/)
Introduction to Clojure (/articles/tutorials/introduction/)
Clojure Editors (/articles/tutorials/editors/)
Clojure Community (/articles/ecosystem/community/)
Basic Web Development (/articles/tutorials/basic_web_development/)
Language: Functions (/articles/language/functions/)
Language: clojure.core (/articles/language/core_overview/)
Language: Collections and Sequences (/articles/language/collections_and_sequences/)
Language: Namespaces (/articles/language/namespaces/)
Language: Java Interop (/articles/language/interop/)
Language: Polymorphism (/articles/language/polymorphism/)
Language: Concurrency and Parallelism (/articles/language/concurrency_and_parallelism/)
Language: Macros (/articles/language/macros/)
Language: Laziness
Language: Glossary (/articles/language/glossary/)
Ecosystem: Library Development and Distribution (/articles/ecosystem/libraries_authoring/)
Ecosystem: Web Development (/articles/ecosystem/web_development/)
Ecosystem: Generating Documentation (/articles/ecosystem/generating_documentation/)
Building Projects: tools.build and the Clojure CLI (/articles/cookbooks/cli_build_projects/)
Data Structures (/articles/cookbooks/data_structures/)
Strings (/articles/cookbooks/strings/)
Mathematics with Clojure (/articles/cookbooks/math/)
Date and Time (/articles/cookbooks/date_and_time/)
Working with Files and Directories in Clojure (/articles/cookbooks/files_and_directories/)
Middleware in Clojure (/articles/cookbooks/middleware/)
Parsing XML in Clojure (/articles/cookbooks/parsing_xml_with_zippers/)
Growing a DSL with Clojure (/articles/cookbooks/growing_a_dsl_with_clojure/)