Using refs in Clojure to Scope State
Posted by Rick on the 25th of October, 2008 at 11:21 pm under Programming. This post has Comments.I mentioned a couple of posts ago about a technique for scoping state I used when I programmed in Scheme more often than I do now. As I’ve been picking up Clojure as my hacking language of preference, I was surprised to find that it doesn’t support the same idioms. In Clojure, any variables referenced when a function is defined in a local scope are unbound when the function is returned to an outer scope. This means the code I wrote in Scheme doesn’t have a direct analogue in Clojure. This was a deliberate decision made by Rich Hickey, Clojure’s designer/author. As he says:
If locals were variables, i.e. mutable, then closures could close over mutable state, and, given that closures can escape (without some extra prohibition on same), the result would be thread-unsafe. And people would certainly do so, e.g. closure-based pseudo-objects. The result would be a huge hole in Clojure’s approach.
The end result is that when you first pick up Clojure, if you try to close a function definition over a variable, you’ll find it is no longer bound when you leave the enclosing scope, and if you try to close over let-bound value (local), you’ll find that is immutable, and can therefore not be used to store state. So how do we accomplish the same result as we did in Scheme?
You use Clojure’s shared transactional memory and create a mutable ref that is modified in a transaction to preserve thread-safety:
(let [z (ref 0)] (defn add [] (dosync (ref-set z (inc @z)))))
We still make use of local binding, but we bind z to a ref, which we can then modify inside of a dosync call. Naturally, an arbitrary number of functions could be defined inside of the let that all shared a reference to z. Once outside of the let, z is no longer accessible to the casual programmer, but the functions still have full access to it, creating a sort of data hiding/state-preserving closure that protects the “private” variable z.
I don’t use this idiom often, but when I want it, it’s exactly what I want, and avoids the need for more complex constructs to simply have a pair of functions share a variable that isn’t modifiable in other parts of the program.