The problem is neither related to apply , nor to concat , nor to mapcat .
dAni's answer , where it reimplements mapcat , accidentally mapcat problem, but the reasoning behind it is incorrect. In addition, his answer points to an article where the author says: "I believe the problem is application." This is clearly wrong, which I will discuss below . Finally, the problem is not related to this other , where a non-lazy assessment is really caused by apply . p>
If you look closely, both dAni and the author of this article implement mapcat without using the map function. In the following example, I will show that the problem is related to how the map function is implemented.
To demonstrate that the problem is not related to either apply or concat , see the following mapcat implementation. It uses both concat and apply , but it achieves complete laziness:
(defn map ([f coll] (lazy-seq (when-let [s (seq coll)] (cons (f (first s)) (map f (rest s))))))) (defn mapcat [f & colls] (apply concat (apply map f colls))) (defn range-announce! [x] (do (println "Returning sequence of" x) (range x))) ;; new fully lazy implementation prints only 5 lines (nth (mapcat range-announce! (range)) 5) ;; clojure.core version still prints 32 lines (nth (clojure.core/mapcat range-announce! (range)) 5)
Complete laziness in the above code is achieved by overriding the map function. In fact, mapcat implemented exactly the same as in clojure.core , but it works completely lazy. The above map implementation is slightly simplified for the sake of example, since it supports only one parameter, but even its implementation with the whole variational signature will work the same way: complete laziness . So, we have shown that the problem here is not with apply and with concat . In addition, we showed that the real problem should be related to how the map function is implemented in clojure.core . Let's take a look at this:
(defn map ([f coll] (lazy-seq (when-let [s (seq coll)] (if (chunked-seq? s) (let [c (chunk-first s) size (int (count c)) b (chunk-buffer size)] (dotimes [i size] (chunk-append b (f (.nth ci)))) (chunk-cons (chunk b) (map f (chunk-rest s)))) (cons (f (first s)) (map f (rest s))))))))
You can see that the clojure.core implementation is exactly the same as our "simplified" version before, except for branching the true if (chunked-seq? s) . Essentially clojure.core/map has a special case for handling input sequences, which are sequence sequences.
Separated sequences compromise laziness, evaluating in pieces 32 instead of strictly one at a time. This becomes painfully apparent when evaluating deeply nested fragmented sequences, as in the case of subsets . A number of sequences were introduced in Clojure 1.1, and many core functions have been updated to recognize and handle them differently, including map . The main goal of their implementation was to improve performance in certain flow processing scenarios, but perhaps they significantly complicate the discussion about the characteristics of laziness in the program. You can read on fragmented sequences here and here . Also check this question here .
The real problem is that range returns chunked seq and is used inside subsets . The fix recommended by David James corrects subsets to undo the sequence created by range internally.