Search code examples
clojurelazy-evaluation

map not quite lazy?


map doesn't seem quite as lazy as I would like, in this example map calls the function one time as I would expect:

(first (map #(do (println "x: " %) %) '(0 1)))

but in these two examples it calls the function two times:

(first (map #(do (println "x: " %) %) '[0 1]))
(first (map #(do (println "x: " %) %) (doall (range 2))))

What is the underlying principle for making the choice to be lazy or not?

Is there a way to guarantee total laziness?

Thanks for your time.


Solution

  • Map (and similar HOFs that work on collections) works on the sequence abstraction over collections: it creates a sequence from the passed collection (seq coll) and works on the returned sequence afterwards. PersistentList ('(0 1) is instance of PersistentList) implements ISeq through the ASeq extension, so seq function returns the list itself. In case of PersistentVector ([1 2]), the seq function returns a chunked sequence (performance reasons, it evaluates chunk (32 elts) of data in one step, but returns only the requested element; the next chunk is calculated when the current chunk is exhausted).

    When testing this behaviour, try to test with collections with count > 32 ((vec (range 40)) returns a vector of items 0-39):

    => (first (map #(do (println "x: " %) %) (vec (range 40))))
    x:  0
    x:  1
    x:  2
    x:  3
    ...
    x:  30
    x:  31
    0
    

    As you can see, the whole chunk (elements 0-31) is evaluated when accessing the first element, the remaining elements (32-39) will be evaluated when requesting the first element from the next chunk.

    In case of collections that are list, chunked seq is not used and only the first item is evaluated ((apply list (range 40)) returns a list of items 0-39):

    => (first (map #(do (println "x: " %) %) (apply list (range 40))))
    x:  0
    0