8

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?)
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192

2 Answers2

8

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.

mike3996
  • 17,047
  • 9
  • 64
  • 80
  • 2
    This isn't a how-to mix side-effects and laziness question -- this is a "whoa, that was weird, why did that happen and how do we avoid that in the future?" question. Am I right in interpreting your answer as: Clojure evaluates impure/Java calls strictly, it's the programmer's responsibility, and don't mix impure with laziness? – Matt Fenwick Oct 19 '11 at 16:46
  • 3
    @MattFenwick, basically Clojure always evaluates strictly. Lazy seqs basically use the same tricks as python with their generators etc. Strict evals in lazy clothes. :) – mike3996 Oct 19 '11 at 16:52
2

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.

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
  • 1
    Ok, as progo stated, clojure evaluates strictly; *for function calls and bindings*. Macros can do whatever they like with their arguments. (Lazy) seqs are abstractions and strict/eager evaluation doesn't really apply to them directly; a lazy seq evaluates to itself - it's only when you try to examine elements in them that you're certain those values are realized (though it may already have happened before then). – Joost Diepenmaat Oct 19 '11 at 18:00