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 def
forms are compiled.
Note that using def
forms not at top-level (or perhaps inside a top-level let
-- 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 def
story:
Compilation of def
forms:
When a def
form is encountered, a Var of the appropriate name is created at that moment by the compiler in the current namespace. (Attempting to def
a Var outside the current namespace by using a namespace-qualified symbol as the name argument to def
results in an exception). That Var is at first unbound and stays unbound until the def
is actually executed; for a top-level def
, that'll be right away, but for a def
hidden inside a function's body (or inside a let
form -- see below), that'll be when the function is called:
;;; in the user namespace:
(defn foo []
(def bar "asdf")
:done)
; => #'user/foo
bar
; => #<Unbound Unbound: #'user/bar>
;;; let's change the namespace and call foo:
(ns some.ns)
(user/foo)
; => :done
bar
; exception, the bar Var was created in the user namespace!
user/bar
; => "asdf"
; the Var's namespace is fixed at compile time
The first example -- with the do
form:
Top level do
s are treated as if their contents were spliced into the flow of code at the place where the do
occurs. So if you type (do (println ...) (def ...) (println ...))
at the REPL, that's equivalent to typing in the first println
expression, then the def
, then the second println
expression (except the REPL only produces one new prompt).
The second example -- with future
:
(future ...)
expands to something close to (future-call (fn [] ...))
. If ...
includes a def
form, 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, thus resolve
will be able to find it.
As a side note, let's have a look at a similar snippet and its output:
(let []
(println (resolve 'c))
(def c "c")
(println (resolve 'c)))
; #'user/c
; #'user/c
; => nil
The reason is as before with the extra point that let
is first compiled, then executed as a whole. This is something one should keep in mind when using top-level let
forms 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With