Why I can do this:
> (set! *unchecked-math* true)
true
> (set! *warn-on-reflection* false)
false
but can't do this:
> (def ^:dynamic *x*)
#'user/*x*
> (set! *x* 1) ;; no luck, exception!
In Clojure, variables are defined by the 'def' keyword. It's a bit different wherein the concept of variables has more to do with binding. In Clojure, a value is bound to a variable.
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.
A namespace is both a name context and a container for vars. Namespace names are symbols where periods are used to separate namespace parts, such as clojure. string . By convention, namespace names are typically lower-case and use - to separate words, although this is not required.
Is it possible the builtin dynamics are implicitly wrapped in a binding form by the runtime? Because this works, for example:
user=> (def ^:dynamic *x*)
user=> (binding [*x* false] (set! *x* true))
true
user=>
One thing to note is that the documentation explicitly says that its an error to try and modify the root binding via set!
, see:
http://clojure.org/reference/vars
It's also possible that the builtins are treated exceptionally, for example if you look at the metadata of x:
user=> (meta #'*x*)
{:dynamic true, :line 1, :column 1, :file "/private/var/folders/8j/ckhdsww161xdwy3cfddjd01d25k_1q/T/form-init5379741350621280680.clj", :name *x*, :ns #object[clojure.lang.Namespace 0x6b8f00 "user"]}
It's marked as dynamic, whereas *warn-on-reflection*
is not marked as dynamic but still works in a binding form:
user=> (meta #'*warn-on-reflection*)
{:added "1.0", :ns #object[clojure.lang.Namespace 0x377fc927 "clojure.core"], :name *warn-on-reflection*, :doc "When set to true, the compiler will emit warnings when reflection is\n needed to resolve Java method calls or field accesses.\n\n Defaults to false."}
user=> (binding [*warn-on-reflection* true] (set! *warn-on-reflection* false))
false
user=>
Presumably this is for backward compatibility because in earlier versions of clojure vars with earmuffs (stars in each side) were dynamic by convention. But anyway this just goes to show that builtins are treated slightly differently.
Now, I decided to take this a bit further, and grep
the source code of clojure, looking for warn-on-reflection
, which leads me to the constant WARN_ON_REFLECTION
, which leads me to lines of code like this in RT.java
:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L467
Var.pushThreadBindings(
RT.mapUniqueKeys(CURRENT_NS, CURRENT_NS.deref(),
WARN_ON_REFLECTION, WARN_ON_REFLECTION.deref()
,RT.UNCHECKED_MATH, RT.UNCHECKED_MATH.deref()));
Which leads me to believe that my initial assumption was correct, that certain special global vars are implicitly wrapped in a thread local binding.
EDIT:
As mentioned in a comment, you can use clojure.core/push-thread-bindings
, but be sure to follow the advice of the documentation and wrap in try/catch/finally with pop-thread-bindings
in the finally block. And at that point you would have just re-implemented binding
(e.g. run (source binding)
at the repl), which is probably why the doc explicitly warns that push-thread-bindings
is a low level function and binding
should be preferred.
Perusing the doc, you'll find
user=> (doc thread-bound?)
-------------------------
clojure.core/thread-bound?
([& vars])
Returns true if all of the vars provided
as arguments have thread-local bindings.
Implies that set!'ing the provided vars will succeed.
Returns true if no vars are provided.
in particular:
Implies that set!'ing the provided vars will succeed
so, this means you can check if set!
is possible as follows:
user=> (thread-bound? #'*x*)
false
user=> (thread-bound? #'*unchecked-math*)
true
which means you can only set!
thread-bound vars, which your *x*
is not (yet).
PS: in Kevins answer you'll see Var.pushThreadBindings
which presumely is actually avaliable as clojure.core/push-thread-bindings
- if you wan't to dig deeper.
According to the reference on Vars, you can only use set!
assignment on thread-bound vars:
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.
However, the built-in dynamic vars like *warn-on-reflection*
have thread-local bindings, thus you can freely use set!
on them:
(thread-bound? #'*unchecked-math*)
=> true
whereas (def ^:dynamic *x*)
only creates a root binding:
(thread-bound? #'*x*)
=> false
The binding
macro creates a new scope for a dynamic Var; on the low level, it temprorarily 'pushes' bound values of given Vars to the current thread, and then 'pops' them upon exiting the body of the macro.
(binding [*x* 1]
(thread-bound? #'*x*))
=> true
(binding [*x* 1]
(set! *x* 2))
=> 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With