Why I can set! builtin dynamic (?) Clojure vars?
Asked Answered
E

3

6

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!
Encrimson answered 10/8, 2016 at 15:13 Comment(0)
H
5

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.

Humanoid answered 10/8, 2016 at 15:52 Comment(2)
Now I see, thanks! Also that's why I couldn't find the definition of *warn-on-reflection* in clojure.core.Encrimson
You might want to add that you can probably make your own dynamic variable act the same with clojuredocs.org/clojure.core/push-thread-bindings.Pearlstein
J
4

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.

Jetsam answered 10/8, 2016 at 16:22 Comment(0)
B
1

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
Baize answered 10/8, 2016 at 16:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.