Welcome! Please see the About page for a little more info on how this works.

0 votes
in Syntax and reader by

Hi,

(def cnt (atom 0))
(defn up[] (swap! cnt #(inc %)))
(defn down[] (swap! cnt #(dec %)))
(defn shift-by[offset] (swap! cnt #(+ % offset)))
(dotimes [_ 10] (up))
(println "cnt up => " @cnt)
(loop [i 0] (if (< i 10) (do (down)(recur (inc i)))))
(println "cnt down => " @cnt)
(def cnt' (for[i (range 5)] (shift-by i)))
(println "cnt shift1 => " @cnt)
(println "cnt' => " cnt')
(println "cnt shift2 => " @cnt)

cnt up => 10
cnt down => 0
cnt shift1 => 0
cnt' => (0 1 3 6 10)
cnt shift2 => 10

why is shift1 shows 0 and shift2 show 10?

Regards,
Daniel

1 Answer

0 votes
by

My first guess would be for's laziness. Try (def cnt' (vec (for[i (range 5)] (shift-by i)))) and see if that changes things.

by
yes, it does change things.

(doall ...) works too

I think my expectation was that using atom will force the lazy expressions to evaluate
by
Printing the sequence (cnt') is what realizes the lazy sequence that for produces.
by
>>>Printing the sequence (cnt') is what realizes the lazy sequence that for produces.

yes, so why does not clojure wait for cnt' to evaluate and prints 0?
by
Because `for` returns (evaluates to) a lazy sequence which means it will only realize individual elements as they are consumed - this is a rough approximation but is more or less what clojure's laziness is about. One of the reasons for laziness is exactly that the entire (potentially infinite) sequence won't have to be realized so operations producing a lazy seq (like for) can return quickly.

So the value of cnt' is a lazy seq. When you print it, the printer sees that it's a sequence so it iterates its elements, which will cause them to be realized, triggering the side effects in the code that realizes the elements.

vec produces a vector which is not lazy so it will need all the elements in cnt', also causing them to be realized. doall is specifically for the purposes of realizing all elements.

To answer the question "why does not clojure wait for cnt' to evaluate" - it does wait for it, but as I wrote above, cnt' evaluates to a lazy seq, which doesn't guarantee that any of its elements are realized yet.

I consider lazy effects a bad practice https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects
by
I think I understand now what happens here

(def cnt (atom 0))
(def cnt' (for[i (range 5)] (shift-by i)))
(println "cnt shift1 => " @cnt)
(println "cnt' => " cnt')
(println "cnt shift2 => " @cnt)

cnt' is lazy, ok
first print reaches for @cnt and therefore lazy sequence is not evaluated
second print goes for cnt' and the lazy sequence is evaluated and @cnt updated
third print just shows updated @cnt

updating @cnt is side-effect of evaluation (could be printing or writing file)
I think clojure does the right thing here
...