Welcome! Please see the About page for a little more info on how this works.

0 votes
in Compiler by
retagged by

I understand the following usage is a bit unorthodox, but when playing around with changing vars to be dynamic, I saw some weird differences in behavior. I wouldn't have been surprised if what I tried didn't work at all, but I was surprised it wasn't consistent.

(def v :init)

(do
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :new ; the value is changed as expected

(let []
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :init ; the code is the same as above except for let

After burrowing down into the compiler, it appears that in ObjExpr.emitVarValue, it thinks v isn't dynamic in the let example, and so returns the root value instead.

Also interestingly, if I deref the var instead, like with (println @#'v), both cases return the :new binding.

2 Answers

+3 votes
by
selected by
 
Best answer

this is the difference between let and do: For toplevel do forms, the body is hoisted up and compiled and executed each one at a time.

So for

(do
  (.setDynamic #'v true) ;1
  (binding [v :new] ;2
    (println v))
  (.setDynamic #'v false) ; 3
 )

The expression is broken up into the three expressions

(.setDynamic #'v true) ; 1

.

(binding [v :new] ; 2
    (println v))

.

(.setDynamic #'v false) ;3

And each expression is then compiled and executed one after the other. Which means the effect of executing expression #1 is visible when compiling expression #2

And the dynamic check for a var happens at compile time, not at runtime, so expression #1 running means that the reference to v in expression #2 is compiled differently to handle a possibly rebound var.

For let expressions, the entire expression is compiled and then executed at once. So when #2 is compiled, #1 hasn't been executed yet, so the var is not marked as dynamic, so the code for handling a bound var is not emitted by the compiler.

+1 vote
by

Var.setDynamic() is not part of the public api for Clojure so I'm not sure this is a relevant question.

by
Yeah, that's sort of what I guessed.

I was experimenting with using `binding` without initially declaring vars as dynamic (because I only want to change a fn for testing purposes, and not in real code), came across .setDynamic(), and thought I'd give it a try.
...