Hum, you're having me question myself.

See, I don't think dynamically scoped Vars are intended for anything in 
particular, they are what they are. Yes, they are useful when you need to 
pass configuration down a call stack, as long as you make sure you handle 
the thread-boundaries along the way. They can also be used to share per 
thread data throughout a thread's execution, which is what ThreadLocal in 
java does. So basically, I see two use cases. One, you want shared read 
only data accessible throughout a call stack (for that, they aren't as good 
as true dynamics, because you'll need to manually handle the thread 
boundaries). Or you want shared writable state per-thread, making this not 
per-thread would be dangerous, and can be done if you re-bind it manually 
across threads or when using constructs that does so like future.

The reason they are made per-thread, and not truly dynamic, as in, taking 
on the value down the stack no matter what, is because threads could 
content on it, and it could cause bugs, so its made per-thread for safety. 
Here's an example of such caveat:

(def ^:dynamic *dd*)

(binding [*dd* (atom "John")]
  (let [a (future (reset! *dd* "Bob"))
        b (future (reset! *dd* "Mike"))]
        @*dd*))

Run this many times, and *dd*'s value could be either Mike or Bob based on 
which threads wins the race.

So the limitation of dynamic scope are inherent to dynamic scoping where 
you have threads. Clojure's attempt at solving this limitation is to make 
them per-thread, with explicit cross thread management through (bound-fn). 
After a few releases, Clojure decided to make future and a few other 
constructs implicitly propagate the dynamic var across threads as that was 
often what people were expecting. All to say, when using dynamic Vars in 
Clojure, you must be thread aware and understand the caveats. There is no 
better way that I'm aware of to handle dynamic scope in a multi-threaded 
environment.

Clojure has root bindings, which Java ThreadLocals does not have. Clojure 
lets you say, if this thread does not have a value for the dynamic Var, 
fetch the root value instead. This is really useful in the use case of read 
only data, like configuration, because you can set a default config. You 
can simulate this in Java with initialValue.

Now, the initialValue lets you do one more thing, it can let you generate 
per-thread values on get, without needing to set it. Such as setting a 
random int on the first get, and subsequent get from that thread will from 
then on return that same generated random int. In Clojure, this is what 
I've seen for such use case:

(def ^:dynamic *id*)

(defmacro with-id [& body]
  `(binding [*id* (rand-int 10000)]
    ~@body))

@(future (with-id (println (str "My request id is: " *id*))
                           "Success"))

It's not as convenient per-say, since you have to be explicit and call the 
macro from each Thread before getting the value, but it solves the use case.

Remember that ThreadLocals are more inconvenient to get though, since you 
need to get the ThreadLocal and then get the value out of it. So in some 
ways, Clojure dynamics can also be seen as more convenient.

Up to now, I feel like there is nothing I can not do with a Clojure Dynamic 
Var that I could with a ThreadLocal. They're not identical, but to me, the 
two fulfills equal use cases. And since under the hood, Clojure dynamic 
Vars are implemented with ThreadLocal, they should perform similarly too.

You say you can pass around references of ThreadLocals, but I'm not sure 
what the point of that would be, like what use case would it allow? In 
general, and even the Java doc page says so, you'll put the ThreadLocal in 
a static variable, i.e., private static final ThreadLocal<Integer> 
threadId. At that point, it's equal to (def ^:dynamic threadId).

Anyways, you can also do that in Clojure, you can pass around the Var such 
as:

(defn printVar [v]
  (println @v))

(with-local-vars [*threadId* 0]
  (.start (Thread. (bound-fn [] (var-set *threadId* 1) (printVar 
*threadId*))))
  (.start (Thread. (bound-fn [] (var-set *threadId* 2) (printVar 
*threadId*)))))

I have a function printVar that takes a Var and prints it. Then I create a 
new Var called *threadId* and I set it to two different values, one for 
each Thread. I pass the Var to my Var printing function, and as you can see 
if you run this, it works like it would in Java if you did the same with 
ThreadLocal, printing different values based on the thread.

I hope this helps.


On Friday, 3 March 2017 07:02:21 UTC-8, Ernesto Garcia wrote:
>
> On Sunday, February 26, 2017 at 6:23:28 AM UTC+1, Didier wrote:
>>
>> Dynamic scoping and Java ThreadLocals gives you equal functionality, so 
>> I'd use them equally. This is because Clojure supports thread bound dynamic 
>> scope.
>>
>
> I wouldn't say that. While dynamically scoped Vars are meant to be context 
> that you implicitly pass down to your call stack, ThreadLocals are 
> references that you can pass around explicitly at will.
>
> Java ThreadLocals also provide an .initialValue() callback to override, 
> which is not provided by Vars. Vars can't even be (idiomatically) 
> initialized to a thread-bound value.
>
> I think that the fact that dynamically scoped Vars are implemented by 
> ThreadLocals is an implementation detail, and it is not a feature, it is 
> more of a limitation. (I guess implementing it otherwise is difficult, if 
> viable at all).
>
> Thanks
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to