When are the various elements of a lazy sequence implemented in clojure?

I am trying to understand when clojure lazy sequences are lazy, and when this happens, and how I can influence these things.

user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4))) #'user/lz-seq user=> (let [[ab] lz-seq]) fn call! fn call! fn call! fn call! nil 

I was hoping to see only two "fn call!" here. Is there any way to handle this? In any case, move on to something that, no doubt, requires only one assessment:

 user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4))) #'user/lz-seq user=> (first lz-seq) fn call! fn call! fn call! fn call! 0 

Is first unsuitable for lazy sequences?

 user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 4))) #'user/lz-seq user=> (take 1 lz-seq) (fn call! fn call! fn call! fn call! 0) 

At this moment, I absolutely do not understand how to access the beginning of my lz-seq toy without realizing it all. What's happening?

+4
source share
4 answers

Clojure sequences are lazy, but for efficiency also alternate, implementing blocks of 32 results at a time.

 =>(def lz-seq (map #(do (println (str "fn call " %)) (identity %)) (range 100))) =>(first lz-seq) fn call 0 fn call 1 ... fn call 31 0 

The same thing happens when you cross border 32 first

 =>(nth lz-seq 33) fn call 0 fn call 1 ... fn call 63 33 

For code where you need to do a lot of work for implementation, Fogus gives you the opportunity to work around chunking , and gives you a hint that the official way to control chunking can continue.

+2
source

I believe that expression expresses consistency. Try replacing 4 with 10000 in the range expression - you will see something like 32 calls to the first eval, which is the size of the fragment.

+2
source

Lazy consistency is where we evaluate consistency as and when necessary. (hence lazy). Once the result is evaluated, it is cached so that it can be reused (and we don’t need to do the work again). If you try to implement an element of a sequence that has not yet been evaluated, clojure evaluates it and returns a value for you. However, he also does some extra work. It suggests that you can evaluate the next element in a sequence and do it for you too. This is to avoid some performance overheads whose exact nature is above my skill level. Thus, when you say (first lz-seq), it actually computes the first, as well as the next few elements in seq. Since your println request is a side effect, you can see how the evaluation happens. Now, if you say (second lz-seq), you will no longer see println, since the result has already been evaluated and cached.

The best way to see your sequence is lazy:

 user=> def lz-seq (map #(do (println "fn call!") (identity %)) (range 400)) #'user/lz-seq user=> (first lz-seq) 

This will print a few calls to "fn!". but not all 400 of them. This is because the first call will actually complete the evaluation of more than one element of the sequence.

I hope this explanation is clear enough.

0
source

I think this is a kind of optimization done by repl. My replica is caching 32 at a time.

 user=> (def lz-seq (map #(do (println "fn call!") (identity %)) (range 100)) #'user/lz-seq user=> (first lz-seq) prints 32 times user=> (take 20 lz-seq) does not print any "fn call!" user=> (take 33 lz-seq) prints 0 to 30, then prints 32 more "fn call!"s followed by 31,32 
0
source

Source: https://habr.com/ru/post/1414315/


All Articles