Reading — step 1 of 5
Learn
Most Clojure vars are immutable from the user's perspective. Dynamic vars can be rebound in a thread-local way using binding.
(def ^:dynamic *log-level* :info)
(defn log [msg]
(when (= *log-level* :debug)
(println "[DEBUG]" msg))
(when (#{:info :debug} *log-level*)
(println "[INFO]" msg)))
(log "hello")
;; [INFO] hello
(binding [*log-level* :debug]
(log "hello"))
;; [DEBUG] hello
;; [INFO] hello
Naming convention: *name* (earmuffs) for dynamic vars. The ^:dynamic metadata is required.
Thread-local: each thread has its own binding stack. Inside binding, the current thread's view of the var changes — other threads are unaffected.
Use cases:
- Logging context (current request, user)
- Database connections
- Configuration override in tests
- Stream redirection
Clojure's standard library has many: *out*, *in*, *ns*, *compile-files*, *print-length*, etc.
(binding [*out* (java.io.StringWriter.)]
(println "goes to writer, not stdout")
(.toString *out*)) ;; "goes to writer, not stdout\n"
Be careful in lazy sequences and async code — bindings are stack-based. If a lazy seq is realized OUTSIDE a binding block, the binding is gone:
(def ^:dynamic *config* nil)
(defn make-seq []
(map (fn [n] {:n n :cfg *config*}) (range 5)))
(let [seq (binding [*config* :testing] (make-seq))]
(first seq))
;; {:n 0 :cfg nil} ;; binding gone — seq not realized inside
(binding [*config* :testing]
(doall (make-seq)))
;; force realization inside the binding
alter-var-root changes the root binding (visible to all threads). Use sparingly — basically global mutable state.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…