Given that this works as I’d expect:
(do
(println (resolve 'a)) ; nil
(def a "a")
(println (resolve 'a))) ; #'user/a
I’d like to understand why this doesn’t:
(future
(println (resolve 'b)) ; #'user/b (shouldn't it be still undefined at this point?)
(def b "b")
(println (resolve 'b))) ; #'user/b
I’d also like to know if this is a proper solution (not exactly solving the same problem, but doing an equivalent job in my context):
(def c (atom nil))
(future
(println @c) ; nil
(reset! c "c")
(println @c)) ; c
This behaviour comes about as a result of the way in which
defforms are compiled.Note that using
defforms not at top-level (or perhaps inside a top-levellet— see below for more comments on this case) is frowned upon as a matter of style in any case. The snippet using an Atom, on the other hand, is fine — no reason not to use it if it does what you want.On to the
defstory:Compilation of
defforms:When a
defform is encountered, a Var of the appropriate name is created at that moment by the compiler in the current namespace. (Attempting todefa Var outside the current namespace by using a namespace-qualified symbol as the name argument todefresults in an exception). That Var is at first unbound and stays unbound until thedefis actually executed; for a top-leveldef, that’ll be right away, but for adefhidden inside a function’s body (or inside aletform — see below), that’ll be when the function is called:The first example — with the
doform:Top level
dos are treated as if their contents were spliced into the flow of code at the place where thedooccurs. So if you type(do (println ...) (def ...) (println ...))at the REPL, that’s equivalent to typing in the firstprintlnexpression, then thedef, then the secondprintlnexpression (except the REPL only produces one new prompt).The second example — with
future:(future ...)expands to something close to(future-call (fn [] ...)). If...includes adefform, it’ll be compiled in the manner we have seen above. By the time the anonymous function executes on its own thread the Var will have been created, thusresolvewill be able to find it.As a side note, let’s have a look at a similar snippet and its output:
The reason is as before with the extra point that
letis first compiled, then executed as a whole. This is something one should keep in mind when using top-levelletforms with definitions inside — it’s generally ok as long as no side-effecty code is intermingled with the definitions; otherwise one has to be extra careful.