0% found this document useful (0 votes)
9 views7 pages

Clojure Guides - Language - Laziness

Uploaded by

eowug
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views7 pages

Clojure Guides - Language - Laziness

Uploaded by

eowug
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

Language: Laziness

This guide covers:

What are lazy sequences


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

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).

What Version of Clojure Does This Guide Cover?


This guide covers Clojure 1.11.

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).

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, 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))

(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765)

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]))

nil

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

(2 3 4 5)

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)))

(0 2 4 6 8 10 12 14 16 18)

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

repeat
iterate
cycle

For example:

(take 3 (repeat "ha"))

("ha" "ha" "ha")

(take 5 (repeat "ha"))

("ha" "ha" "ha" "ha" "ha")


(take 3 (cycle [1 2 3 4 5]))

(1 2 3)

(take 10 (cycle [1 2 3 4 5]))

(1 2 3 4 5 1 2 3 4 5)

(take 3 (iterate inc 1))

(1 2 3)

(take 5 (iterate inc 1))

(1 2 3 4 5)

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, most lazy sequences are chunked (realized in chunks).

For example, in the following code

(take 10 (map inc (range)))

(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).

You can see this in action:


(take 10 (map #(do (println %) (inc %)) (range 100)))

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)

« Language: Macros (/articles/language/macros/) || Language: Glossary » (/articles/language/glossary/)

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/)

Copyright © 2024 Multiple Authors


Powered by Cryogen (https://cryogenweb.org)

You might also like