Concurrency with Vars
When I started using Clojure, I thought I understood what vars were. They’re globals, and they live in a namespace! In fact, vars are a powerful tool for building concurrent, parallelizable systems.
So what is a var? A var is like a global variable—called the “global root binding”. But a var can also be overridden to have a dynamic scope, by declaring it dynamic (all vars are ^:dynamic in Clojure 1.2 and before):
Dynamic scoping (also known as fluid-let in some LISPS and local in Perl) causes the var to have the value that it was bound to most recently on that thread. Another way to think of this is that the value of the var is the top of a stack, and every time a binding is entered it pushes the new value on the stack, and every time the binding is exited it pops the top of the stack off.
This give us a powerful technique: implicit parameters. We can use dynamic vars to pass parameters into functions from much higher in the call stack. We can use this to direct data flow from a central point, and have that change for each thread and each binding (e.g. *out* for the print family of functions). Strategy and configuration are the main usages of dynamic vars.
What if I want to return a fn which executes some deferred work, but I want the dynamic vars when I invoke that fn to be the same as the context it was created? Enter bound-fn and bound-fn* (the former accepting an fntail (e.g. [param1 param2] (body)); the latter, a function.
The above example shows how bound-fn allows you to preserve bindings when you need to run code in another thread. As I mentioned in my previous post, bound-fn plays a key role in the implementation of Futures in Clojure.
The last question I’d like to look at is why we must declare our vars #^:dynamic now when before it wasn’t necessary? If a var is dynamic, then we must always check to see if the thread local stack of values has any elements, and if so, what it’s value is, and if not, then it must retrieve the global root binding. Nondynamic vars just need to retrieve the global root binding. In the name of performance!
Ok, I lied. I also want to mention defonce. This macro essentially expands to:
Not that exciting.