Vars and the Global Environment

Clojure is a practical language that recognizes the occasional need to maintain a persistent reference to a changing value and provides 2 distinct mechanisms for doing so in a controlled manner - Vars and Refs. 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. Every Var can (but needn't) have a root binding, which is a binding that is shared by all threads that do not have a per-thread binding. Thus, the value of a Var is the value of its per-thread binding, or, if it is not bound in the thread requesting the value, the value of the root binding, if any.


The special form def creates (and interns) a Var. If the Var did not already exist and no initial value is supplied, the var is unbound:

user=> (def x)
Var: user/x
user=> x
java.lang.IllegalStateException: Var user/x is unbound.

Supplying an initial value binds the root (even if it was already bound).

user=> (def x 1)
Var: user/x
user=> x
1

Per-thread bindings for one or more Vars can be established via the macro binding:


(binding [bindings* ] exprs*) - Macro

binding => var-symbol init-expr


Creates new bindings for the (already-existing) vars, with the supplied initial values, executes the exprs in an implicit do, then re-establishes the bindings that existed before.

Thus within-thread bindings obey a stack discipline:

user=> (def x 1)
user=> (def y 1)
user=> (+ x y)
2
user=> (binding [x 2 y 3]
         (+ x y))
5
user=> (+ x y)
2

Bindings created with binding cannot be seen by any other thread. Bindings created with binding can be assigned to, which provides a means for nested contexts to communicate with code before it the call stack.


Functions defined with defn are stored in Vars, allowing for the re-definition of functions in a running program. This also enables many of the possibilities of aspect- or context-oriented programming. For instance, you could wrap a function with logging behavior only in certain call contexts or threads.



(set! var-symbol expr)

Assignment special form.


When the first operand is a symbol, it must resolve to a global var. The value of the var's current thread binding is set to the value of expr. Currently, it is an error to attempt to set the root binding of a var using set!, i.e. var assignments are thread-local.

In all cases the value of expr is returned.


Note - you cannot assign to function params or local bindings. Only Java fields, Vars, Refs and Agents are mutable in Clojure.


Interning

The Namespace system maintains global maps of symbols to Var objects. If a def expression does not find an interned entry in the current namespace for the symbol being def-ed, it creates one, otherwise, it uses the existing Var. This find-or-create process is called interning. This means is that, unless they have been unmap-ed, Var objects are stable references, and need not be looked up every time. It also means that namespaces constitute a global environment, in which, as described in Evaluation, the compiler attempts to resolve all free symbols as Vars.


(find-var varsym)

Returns the global var named by the namespace-qualified symbol, or nil if not var with that name.


Non-interned Vars

It is possible to create vars that are not interned. These vars will not be found during free symbol resolution, and their values have to be accessed manually. But they can serve as useful thread-local mutable cells.

(with-local-vars [varbindings+] exprs*)

varbinding=> symbol init-expr

Executes the exprs in a context in which the symbols are bound to vars with per-thread bindings to the init-exprs. The symbols refer to the var objects themselves, and must be accessed with var-get and var-set


(var-get var)

Gets the value in the var


(var-set var val)

Sets the value in the var to val. The var must be thread-locally bound.

Copyright © Rich Hickey