This isn't just a Clojure "Don't". It's a general programming "don't".<p>In my old team we had a semi-serious production bug result from this (it didn't actually kill anyone but stopped the system from being used) because we had an expression with a side effect inside a debug logging statement. The code worked perfectly in a debug build, but in release the DEBUG_PRINTF() equivalent was removed by the C++ preprocessor (so logically equivalent to "!debug && " in front of every line) and a crucial value didn't get updated. It took some tense minutes debugging it on site in front of the client before we figured it out... lesson learned!
This is a sound article - Clojure's laziness is often a source of common gotchas for new users of the language.<p>It's often not clear what is lazy and what isn't - concat is lazy for instance. It isn't externally obvious which of the very common operations are lazy, filter being lazy has bitten me in CLJS a few times too.<p>Adding to the source of the confusion is that the REPL will often realize your side-effects which then won't get evaluated in your runtime environment.<p>One of the errors I used to make earlier on was:<p><pre><code> (map insert-into-db table-rows)</code></pre>
This is part of the reason for monads: they specify the order of operation, which is necessary in a lazy language like Haskell. The `bind` or `flatMap` operation in a monad specifies "what happens next". Once you have defined order of operations you can start to reason about effects.
Something that is specially dangerous is mixing laziness and dynamic vars. If you bind a dynamic var to then return a lazy seq, and the dynamic var is used to generate the seq's elements (e.g. `binding` over `map`, and use the bound var inside the map's `fn`), you may get different results depending on when the seq is evaluated (which is not determined)<p>This made me waste a whole afternoon once. I was even ready to submit a compiler bug!
> In my opinion, the presence of doall, dorun, or even “unchunk” is almost always a sign that something never should have been a lazy sequence in the first place.<p>I’d say it’s a good rule of thumb, but it’s sometimes justified. For example, `line-seq` returns a lazy seq of lines read from a given Reader; the appeal being you can process them one by one, without keeping them in memory all at once. But if you just want them all, you wrap the `line-seq` in a `doall` in a `with-open`.<p>My scraping library, Skyscraper [1], has a similar justification for laziness around side-effects: scraping a site returns a lazy sequence of elements, each corresponding to one page. It's terrifically useful to have that sequence be lazy, and there's unchunking code on Skyscraper to enforce full laziness. Incidentally, I'm rewriting it to be based on core.async, but it has a less functional feel to it.<p>[1]: <a href="https://github.com/nathell/skyscraper" rel="nofollow">https://github.com/nathell/skyscraper</a>
Does mapv not do exactly what the author needs? Ie an unlazy map?
<a href="https://clojuredocs.org/clojure.core/mapv" rel="nofollow">https://clojuredocs.org/clojure.core/mapv</a>
This problem is magnified in ClojureScript doing React.js rendering. React.js renders breadth-first in stratas (compared to a call stack which is depth first). Everyone gets bitten by it once.