Clojure is REALLY Lazy
You may have heard that Clojure is lazy. This basically means that Clojure won’t evaluate an expression unless it absolutely needs to–simple enough, right?
Well, today I experienced a new limit in Clojure’s laziness!
Take Only What You Need
In Clojure, you can map
and filter
over an infinite
sequence, but only receive the value you need!
So something like this will only evaluate values until an even number is found. If the first value is even, then only one value will be evaluated. If it is the then number, then only ten values. Or in the very unfortunate case where the only even number is the last number, then one-trillion values will be evaluated.
(def one-trillion-random-numbers0
[#_...])
(defn first-even-random []
(first (filter even? one-trillion-random-numbers)))
Similarly, if you map over a sequence of values, only those requested will be evaluated! Here, we only increment the first ten numbers in the infinite sequence, because those are the only numbers we care about.
(def some-numbers (take 10 (map inc (range))))
Do Seq What You Must
So, what happens if we do something like this?
(defn send-promotions-to-users [promotions]
(map db/create-promotion! promotions)
(map db/send-promotions! (db/users)))
Well, I’d expect that this function would create some promotions in the database, then send those newly created promotions out to all the users…wrong!
We can say one thing for certain about this function:
it will never create any promotions. map
is so lazy
that it will completely skip over mapping the promotion
collection, because nothing receives its product on the
other end. Depending on how this function is called, it
may even skip sending the promotions as well!
So how can we ensure that promotions are both created
and delivered to users? For this, we’ll need to
use doseq
.
(defn send-promotions-to-users [promotions]
(doseq [promo promotions] (db/create-promotion! promo))
(doseq [user (db/users)] (db/send-promotions! user)))
I just spent about ten minutes trying to figure out why
my test was failing when I “knew” I was updating all
of my database entities (using map
). So keep an eye
out for those lazy functions if you need something to
happen.