1
votes

I am currently going through the book Clojure for the Brave and True, in an attempt to learn the language, but I'm a bit hung up on lazy seqs, and I'm afraid the book does a poor job explaining them. But, according to the book, something like this:

(defn wait-for-a-bit [arg]
  (Thread/sleep 1000))

(defn get-map [seq]
  (map wait-for-a-bit seq))

(time (first (get-map [1 2 3 4 5 6 7 8 9 0])))

Should only take about one second to process, because a value for a lazy seq isn't calculated (realized?) until it's accessed. However, when I run the above code, it takes about ten seconds, so clearly, deferred computation isn't happening. I've looked at the docs on clojuredocs.org, and I think I understand lazy-seq's, but I guess just not in the context of map, reduce, etc.

1
i saw similar. then i tried (time (first (get-map (iterate inc 1000)))) which gave me the more intuitive 1 sec response.user2524973

1 Answers

3
votes

Lazy sequences are chunked by default. So the actual values are computed in chunks of 30 or so. This greatly reduces the context switching overhead in processing them.

here I'll define a sequence 100 items long and look at the first two items:

hello.core> (def foo (map #(do (println "calculating " %)
                               %)
                          (range 100)))
#'hello.core/foo
hello.core> (first foo)
calculating  0
calculating  1
calculating  2
calculating  3
calculating  4
calculating  5
calculating  6
calculating  7

...

calculating  26
calculating  27
calculating  28
calculating  29
calculating  30
calculating  31
0
hello.core> (second foo)
1

This shows that it calculates the first chunk the first time any of the items are realised.

Some sequences are chunked while others are not. It's up to the function that initially creates the seq to decide if it can be chunked. range creates chunked sequences while iterate does not. If we look at the same example again, this time generating the seq using iterate rather than map, we get a non-chunked sequence:

hello.core> (def foo (map #(do (println "calculating " %)
                               %)
                          (take 100 (iterate inc 0))))
#'hello.core/foo
hello.core> (first foo)
calculating  0
0
hello.core> (second foo)
calculating  1
1

and each item is calculated on when it is read. In theory this has efficiency impacts though I have been writing Clojure full time about as long as anyone and have never seen a case where this made a difference in something that was not otherwise poorly designed.