I want to write briefly (ok, not that briefly) about my understanding of Clojure‘s various “update” functions, in other words, functions that take a function argument, apply it to some value and do something with the result. These include
vary-meta, and probably several more. From here on out, I’ll call a function in this family an update function and the function passed to it a transform function.
Note that update functions are most commonly encountered when using Clojure’s reference types, i.e. vars, refs, atoms, and agents. Their signatures are all very consistent:
(<update-fn> old-state <tranform-fn> & args)
That they’re so consistent is a testament to the thought that has gone into the design of Clojure’s core API.
Nonetheless, a newcomer to Clojure, overwhelmed by all the other new concepts they’re learning, might find update functions a little confusing. They might find themselves calling
reset! a lot because
swap! is scary. Of course, if the value passed to
ref-set) depends on the previous value of the ref, you’ve thrown away all the nice concurrency guarantees that atoms and refs are supposed to give you. For example, the prototypical id generator:
; BADBAD Don't do this!
(defn id-generator 
(let [id (atom 0)]
(reset! id (inc @id)))))
No No No No No
Of course, everyone knows to use
(swap! id inc) for a canonical example like this, but in the thick of a larger app, feeling like you’re in over your head, it’s easier to make mistakes.
So, learning to read (and write) an update function can take you a long way along the road toward writing more idiomatic Clojure. For someone with an OO background, it might be easier if we mentally re-wrote the signature above like this:
state = transform_fn(state, arg0, arg1, ...);
that is, we’re applying some transformation to the current value of
state and storing the value back in
swap! and friends are just a function call in disguise. Their signatures are completely consistent with Clojure’s argument order conventions, but they slightly obscure what’s going on because the state and transform function are switched. This makes sense since
state is the important argument, but it took me a little while to realize this and make the mental adjustment.
Once I reached this conclusion, I found writing in a functional style with state transitions much more straightforward.
Note that I find this a useful way to *read* update functions. It’s not a replacement for thinking hard about the semantics of the update function you’re using
Here are some general guidlines that I find helpful:
and finally, as a wise man once said “functions and data!”
In conclusion, I hope this sheds some light on an area of Clojure that I’ve personally found to require a great deal of mental deprogramming. As I’ve gained confidence with these concepts, my code has been easier to read, easier to test, and easier to reason about. Happy Clojuring.