Understanding the difference between lexical and dynamic scope in Clojure

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 []

  ;; dynamically rebind dynamic-var
  (binding [*dynamic-var* "this is some new content for the dynamic var"]

  ;; locally rebind non-dynamic-var
  (let [non-dynamic-var "this is some new content that won't be used"]
  ;; lexically (and globally) rebind non-dynamic-var
  (def non-dynamic-var "this is some new content that will be used")

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 some-function is:

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 binding and def have changed the value of each var when we call function-using-dynamic-var and 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 non-dynamic-var.

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 let and 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 with-redefs?

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 ^:dynamic.