8
votes

We stumbled upon an issue in our code today, and couldn't answer this Clojure question:

Does Clojure evaluate impure code (or calls to Java code) strictly or lazily?

It seems that side-effects + lazy sequences can lead to strange behavior.


Here's what we know that led to the question:

Clojure has lazy sequences:

user=> (take 5 (range)) ; (range) returns an infinite list
(0 1 2 3 4)

And Clojure has side-effects and impure functions:

user=> (def value (println 5))
5                               ; 5 is printed out to screen
user=> value
nil                             ; 'value' is assigned nil

Also, Clojure can make calls to Java objects, which may include side-effects. However, side-effects may interact poorly with lazy evaluation:

user=> (def my-seq (map #(do (println %) %) (range)))
#'user/my-seq
user=> (take 5 my-seq)                               
(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
0 1 2 3 4)

So it returned the first 5 elements, but printed the first 31!

I assume the same kinds of problems could occur if calling side-effecting methods on Java objects. This could make it really hard to reason about code and figure out what's going to happen.


Ancillary questions:

  • Is it up to the programmer to watch out for and prevent such situations? (Yes?)
  • Besides sequences, does Clojure perform strict evaluation? (Yes?)
2

2 Answers

8
votes

Clojure's lazy seqs chunk about 30 items so the little overhead is further reduced. It's not the purist's choice but a practical one. Consult "The Joy of Clojure" for an ordinary solution to realize one element at time.

Lazy seqs aren't a perfect match for impure functions for the reason you encountered.

Clojure will also evaluate strictly, but with macros things are a bit different. Builtins such as if will naturally hold evaluating.

2
votes

Lazy constructs are evaluated more or less whenever is convenient for the implementation no matter what's referenced in them. So, yes, it's up to the programmer to be careful and force realization of lazy seqs when needed.

I have no idea what you mean by strict evaluation.