问题
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
回答1:
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 todef
a Var outside the current namespace by using a namespace-qualified symbol as the name argument todef
results in an exception). That Var is at first unbound and stays unbound until thedef
is actually executed; for a top-leveldef
, that'll be right away, but for adef
hidden inside a function's body (or inside alet
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 thedo
occurs. So if you type(do (println ...) (def ...) (println ...))
at the REPL, that's equivalent to typing in the firstprintln
expression, then thedef
, then the secondprintln
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 adef
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, thusresolve
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-levellet
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.
来源:https://stackoverflow.com/questions/12059936/clojure-global-variable-behavior-in-a-threaded-environment