Monday, June 7, 2010

clojure.core/lazy-seq makes sense

clojure.core/lazy-seq makes sense

Few days ago on #clojure bartj asked about an infamous wiki entry.
I remember that while ago, when I tried to understand laziness in Clojure I stumbled upon this entry as well,
and it caused me only confusion, as it is totally outdated.
Let's try to shed some light on it.

Not lazy seq

When you construct a seq in non lazy way you should expect that all elements are realized at creation time.
(let [a (cons 1 (cons 2 nil))] (first a))
=> 1
Nothing special here. Let's introduce side effects to see what happens.
(let [a (cons
(do (println "one") 1)
(cons
(do (println "two") 2)
nil))]
(first a))
: one
: two

=> 1
As you expected all elements has been realized while constructing a, though we only needed the first element.
And that's exactly where laziness kicks in.

lazy-seq

First lets construct lazy seq with no side effects.
(let [a (lazy-seq (cons 1
(lazy-seq (cons 2 nil))))]
(first a))
=> 1
So what is new? You have to type more and effect is the same. Why?
Adding side effects will help understanding it.
(let [a (lazy-seq (cons
(do (println "one") 1)
(lazy-seq (cons
(do (println "two") 2) nil))))]
(first a))
: one
=> 1
Now you should be able to see the benefit. Result is the same, except the whole sequence hasn't been realized,
except for the first element, as it was the only element that was needed.

Laziness is a very powerful tool:
  • you can construct infinite sequences,
  • have side effects create elements on demand, if seq construction is heavy, you might want to only issue creation of elements you'll use,
  • it has same interface as non-lazy seq, code using it does not have to care about it.

To sum it up

(let [a (cons
(do (println "one") 1)
(cons
(do (println "two") 2) nil))])
: one
: two

=> nil
We didn't need even single element but the whole seq was realized.
(let [a (lazy-seq (cons
(do (println "one") 1)
(lazy-seq (cons
(do (println "two") 2) nil))))])
=> nil
If not even a single element is needed, than not even a single element will be realized.
So there you have it clojure.core/lazy-seq explained.