Dynamic binding lets a function read a temporary value from its calling context without receiving it as an argument. A Clojure var normally has a root binding visible to all threads. Mark it ^:dynamic to allow thread-local bindings; by convention, its name has surrounding asterisks, such as *trace*. section 16.0 section 23.0
Scope a value with binding
(def ^:dynamic *mode* :normal) (binding [*mode* :debug] [(binding [*mode* :quiet] *mode*) *mode*]) ;; [:quiet :debug] *mode* ;; :normal Each binding supplies a value for the current thread. Nested bindings stack: leaving the inner scope restores :debug, and leaving the outer scope exposes the unchanged root value. section 16.0 section 16.4
This is useful for per-request tracing: bind an initially empty *trace* around a request, and functions deeper in the call stack can add messages without passing the trace through every call. Other threads do not automatically share that binding. When work starts on a new thread, bound-fn or bound-fn* can wrap its function to propagate the current bindings. section 16.4
(in-ns 'user)
(def ^:dynamic *trace*) ; #1
(defmacro trace! [msg & body] ; #2
`(do
(when (thread-bound? #'*trace*)
(set! *trace* (conj *trace* ~msg)))
~@body))
(defn params [query] ; #3
(let [pairs (clojure.string/split query #"&")]
(trace! (format "Handling params %s" pairs)
(->> pairs
(map #(clojure.string/split % #"="))
(map #(apply hash-map %))
(apply merge)))))
(defn handle-request [{:strs [op arg1 arg2]}] ; #4
(let [op (resolve (symbol op))
x (Integer. arg1)
y (Integer. arg2)]
(trace!
(format "Handling request %s %s %s" op x y)
(op x y))))
(binding [*trace* []] ; #5
(let [query "op=+&arg1=1&arg2=2"
res (handle-request (params query))]
(pprint *trace*)
res))
;; ["Handling params [\"op=+\" \"arg1=1\" \"arg2=2\"]"
;; "Handling request #'clojure.core/+ 1 2"]
;; 3
The request-tracing example shows *trace* shared by parameter parsing and request handling inside one binding. section 16.4
Dynamic vars also configure standard-library behavior. The current namespace is held in *ns*, while *loading-verbosely* can enable loading output around an operation. Timing matters: *assert* controls assertion expansion at compile time, so binding it to false around an already compiled assert does not disable that assertion; evaluating the form under the binding can. section 16.6 section 23.4
Prefer binding for ordinary scoped changes. Lower-level push-thread-bindings and pop-thread-bindings require explicitly popping bindings in a finally block; binding handles that scope for you. section 16.4
