On Jan 3, 10:31 pm, Chas Emerick <
cemer...@snowtide.com> wrote:
> I ran into an odd exception when attempting to eval some simple code
> in a new Thread:
>
> clojure.lang.Compiler$CompilerException: null:0: Var null is unbound.
> The above was thrown when (eval '(1 2 3)) was called in a new Thread.
> I ran things through a debugger, and the problem ended up being that
> the clojure.lang.Compiler.LOADER var's value was null in the new
> thread context. Looking at what Repl does to set up the thread
> locals, I now execute this in every new thread I create where I'm
> evaling code:
>
> (. clojure.lang.Var (pushThreadBindings {(. clojure.lang.Compiler
> LOADER) (new clojure.lang.DynamicClassLoader)}))
>
> I don't mind this, but it seems that this sort of setup (which is
> duplicated in the Repl, implementations of load and load-file, etc)
> should happen automatically and lazily. Alternatively, a first easy
> step would be to have a thread-setup function in the std lib that took
> care of such things.
>
It's not that straightforward. First off, because it is a paired push/
pop operation, it's not equivalent to a lazy initialization. Second,
different apps might have different policies re: the retention of the
classloader. Third, it's more of a general problem than just LOADER/
IMPORTS/REFERS etc - when creating a new thread an app must decide
what the dynamic context will be and establish bindings for any vars
it wants the thread to see.
Part of the nuisance of this, and the source of repetition of all
patterns that require matched operation, is that Java doesn't have
closures, so you can't do stuff like:
inMyStandardEvalContext(doSomething)
without wrapping doSomething in a Runnable/Callable/IFn.
Obviously it's easy in Clojure.
In any case, LOADER is still an implementation detail, so I've made it
so that Compiler.eval will set it up (and tear it down!) if it is not
otherwise set up when eval is called.
Rich