What is the difference between lexical and dynamic scope in Clojure? A question that I asked myself several times because the difference was difficult to grasp and to remember for me. I’ll try to give a short explanation here.
What is scope?
First, what is scope after all? Usually in programming at several points there need to be values saved in order to work with them. These values need to be referenced somehow. That’s why we usually give these values names, so we can reuse them later. Now the scope of a value is the location in the program where a name is referring to a certain value. In lexical scoping, that will correspond somehow with the area in the code where a value has been declared, while in dynamic scoping this depends on the runtime stack of declarations.
A Clojure example
To understand this, let’s first look at an example:
(def non-dynamic-var "this is a non dynamic var") (def ^:dynamic *dynamic-var* "this is a dynamic var") (defn function-using-dynamic-var  (println *dynamic-var*)) (defn function-using-non-dynamic-var  (println non-dynamic-var)) (defn some-function  (function-using-dynamic-var) ;; dynamically rebind dynamic-var (binding [*dynamic-var* "this is some new content for the dynamic var"] (function-using-dynamic-var)) (function-using-non-dynamic-var) ;; locally rebind non-dynamic-var (let [non-dynamic-var "this is some new content that won't be used"] (function-using-non-dynamic-var)) ;; lexically (and globally) rebind non-dynamic-var (def non-dynamic-var "this is some new content that will be used") (function-using-non-dynamic-var))
We are declaring a normal and a dynamic var (by adding the
^:dynamic metadata part; the leading and trailing asterisks in the name are a naming convention for dynamic vars) and for each one a function that outputs them. Then we call each function before and after rebinding/redefining the vars and see what changes.
Output from a call of
this is a dynamic var this is some new content for the dynamic var this is a non dynamic var this is a non dynamic var this is some new content that will be used
We can see here that both
def have changed the value of each var when we call
function-using-non-dynamic-var again. The difference is that
binding only changes the value of
*dynamic-var* within the scope of the
binding expression, while
def changes the root definition of
non-dynamic-var from the point of execution on. What does not work here, is using
let to rebind
non-dynamic-var since the change is only local and won’t affect the top-level
Lexical scope vs. dynamic scope
In lexical scope, a variable always refers to its local lexical environment. The lexical environment is dependent only on the program text. Thus, a static analysis (at compile time) of the program text can always tell us the scope of a variable. That’s why lexical scope is also called static scope.
In difference to static scope dynamic scope is dependent on the runtime call stack. This means a program needs to be executed to determine the scope of dynamic variables. Each identifier has a global stack of bindings and the content of a variable is difficult to reason about just by looking at the code, since there may be several different dynamic contexts in which a piece of code can be invoked.
A note on local scope and the method with-redefs
In the example above we see that local scope, as we know it from other languages like Java, in Clojure can only be achieved with
binding, not with
def. The latter doesn’t provide us any local scope since it always applies changes globally.
Note though that there is another possibility to achieve local scope: the method
with-redefs can change the root binding of a (non-dynamic) var within its scope. You might wonder now: What’s the difference between binding and
with-redefs and why do we need dynamic vars after all? Can’t we achive the same thing with
It’s a little confusing, yes. The difference is that
binding only rebinds the vars thread-locally while bindings made by
with-redefs are visible in all threads. The takeaway is that dynamic vars and
binding are a more controlled way to achieve the binding and you should always use them if you can. Only use
with-redefs for testing and when you don’t have another choice because the var you want to rebind lies out of your control and wasn’t declared