I'm learning Clojure and am playing around with dynamic scope. I've discovered a few things:
- Adding
{:dynamic true}
metadata to an existing Var doesn't actually make the Var dynamic. I can kind of see why you'd want this to be the case, but it can be misleading. - You can see if a Var is actually dynamic with
(.isDynamic #'whatever)
- You can (but probably shouldn't?) change whether a Var is dynamic with
(.setDynamic #'whatever)
In short, it looks like the dynamic nature of a Var is stored in the guts of the clojure.lang.Var class itself, and setting ^:dynamic
metadata at def time only will affect that internal state. Changing metadata afterward doesn't appear to affect it. OK, fine.
I discovered this when I was looking at the dynamic variables in namespace clojure.core. I checked to see what Vars in that namespace have :dynamic
metadata set:
user=> (def metadyn (map key (filter #(-> % key resolve meta :dynamic) (ns-publics 'clojure.core))))
#'user/metadyn
user=> metadyn
(*3 *2 *print-level* *data-readers* *e *print-length* *1 *verbose-defrecords*
*clojure-version* *default-data-reader-fn* pr)
Rather fewer than I expected, missing things like *out*
and *in*
.
I checked to see what Vars actually are dynamic:
user=> (def realdyn (map key (filter #(-> % key resolve .isDynamic) (ns-publics 'clojure.core))))
#'user/realdyn
user=> realdyn
(*compile-path* *3 *2 *ns* *print-level* *allow-unresolved-vars* *data-readers*
*unchecked-math* *math-context* *read-eval* *compile-files* *command-line-args*
*warn-on-reflection* *e *flush-on-newline* *out* *print-length* *1 *file* *verbose-defrecords*
*clojure-version* *use-context-classloader* *err* *default-data-reader-fn* *agent* pr
*print-dup* *print-readably* *fn-loader* *print-meta* *in* *source-path* *compiler-options* *assert*)
Lots more.
So, the following Vars are dynamic but don't claim to be in their metadata:
user=> (clojure.set/difference (set realdyn) (set metadyn))
#{*compile-path* *ns* *allow-unresolved-vars* *unchecked-math* *math-context* *read-eval*
*compile-files* *command-line-args* *warn-on-reflection* *flush-on-newline* *out* *file*
*use-context-classloader* *err* *agent* *print-dup* *print-readably* *fn-loader* *print-meta*
*in* *source-path* *compiler-options* *assert*}
My question is simply: is there any significance to this, something I'm missing? Or, is it just a case of sloppy bookkeeping in the Clojure implementation?
No practical use for this; just trying to Understand.