Can’t dynamically bind non-dynamic var in Clojure 1.3

That’s what I enjoy the most – a series of short and concise readings in the early morning that eventually lead to a solution of a problem I could not likely have tackled successfully otherwise.

I stumbled upon Hello Web : Dynamic Compojure Web Development at the REPL by John Lawrence Aspden. It took me about a minute or two to digest its content since it’s nothing new to me. I knew Compojure. Yet it struck me when Apache Maven’s used not leiningen as the Clojure-based tool for Clojure (which, as a matter of fact, arrived at 1.5.0 release a couple of hours ago!).

It was the comment of Shantanu Kumar on this article that made my day. I remember I’ve read about the magic Shantanu referred to, but I also remember having hard time understanding such a basic concept of Vars in Clojure.

“Vars provide a mechanism to refer to a mutable storage location that can be dynamically rebound (to a new storage location) on a per-thread basis.”

I fired up Clojure REPL and played with the vars to appreciate their dynamicity.

user=> (def x 10)
#'user/x
user=> x
10
user=> (def vx (var x))
#'user/vx
user=> @vx
10
user=> vx
#'user/x
user=> (type vx)
clojure.lang.Var
user=> (type x)
java.lang.Long
user=> (def x -10)
#'user/x
user=> @vx
-10

I thought I’d finally got the gist of the Vars until I run across an issue with reassigning def’d Vars with binding as described in Clojure’s official documentation – Vars and the Global Environment.

user=> (def x 1)
#'user/x
user=> (binding [x 10] x)
IllegalStateException Can't dynamically bind non-dynamic var: user/x  clojure.lang.Var.pushThreadBindings (Var.java:339)

It happened with the recent version of Clojure 1.3.0-alpha6 and at first glance contradicted what’s in the official documentation.

user=> *clojure-version* 
{:major 1, :minor 3, :incremental 0, :qualifier "alpha6"}

The very first match in Google yielded a thread in the Clojure mailing list – “Vars” problem. Doh, if only I’d read the mailing list on a regular basis, I’d certainly not have struggled with such basic issues.

The solution in Clojure 1.3 is that “variables must be declared dynamic in order to change their bound value: (def ^:dynamic x 1)” as pointed out by Sean Corfield.

user=> (def ^:dynamic x 1)
#'user/x
user=> (binding [x 10] x)
10

It explained how to sort it out (or work it around), but left a question why the change was made at all.

Digging into the changes for Clojure 1.3 I found the explanation – cf. 4 Changes from 1.3 Alpha 1 to 1.3 Alpha 2 (10/10/2010):

“code path for using vars is now *much* faster for the common case, and you must explicitly ask for :dynamic bindability”

It turns out that the change made Clojure faster. Good. My clients will appreciate my advice when I tell them to upgrade to 1.3 for performance reasons and they get to know my daily rate (I’d likely better off doing it the other way around :-))

I spent no time digging further to answer the question “How came it’s faster?”, albeit my wish to know the detail lurks within me. I’d very much appreciate an article that would sum it up so one (me including) would not be “encouraged” to read the source code of Clojure 1.3 which may eventually lead to false conclusions or possible misunderstandings when the change around :dynamic may have spread across many files.

Just before I published the blog entry, I gave another shot and…wait, I may have found another issue (!) Why does the code print 2 not 11?!

user=> (def ^:dynamic x 1)
#'user/x
user=> (def f (inc x))
#'user/f
user=> (binding [x 10] f)
2

That really struck me and kept busy for a while, but just for the sake of a “Aha” moment I’m able to mentally suffer more often.

Have a closer look at the definition of the var f. It is not a function, but a var – a constant in Java – so it’s bound to a value of (inc x) upon definition. To make it more versatile use defn or point to an anonymous function with #(). Easy, isn’t it?

user=> (def ^:dynamic x 1)
#'user/x
user=> (defn f [] (inc x))
#'user/f
user=> (binding [x 10] (f))
11
user=> (def f #(inc x))
#'user/f
user=> (binding [x 10] (f))
11
user=> (binding [x 20] (f))
21

Note the difference while calling out the var f which is a function now (or a var referencing a function). I’m glad I could figure it out myself.

Be Sociable, Share!
This entry was posted in Languages.

2 Responses to Can’t dynamically bind non-dynamic var in Clojure 1.3

  1. fogus says:

    Why faster?

    Without digging into the technical details think of it in this way. If all Vars allow dynamic binding, then every access requires a check to see if a non-root value exists in the dynamic environment. This check was being paid for every access even if dynamic binding was never used. By making dynamic bindability optional, Clojure can eliminate that check by default making Var lookups much quicker.

  2. Pingback: Can’t dynamically bind non-dynamic var in Clojure 1.3 and on | Japila :: verba docent, exempla trahunt

Leave a Reply

%d bloggers like this: