如果这能像我预期的那样工作:
(do
(println (resolve 'a)) ; nil
(def a "a")
(println (resolve 'a))) ; #'user/a
我想知道为什么没有:
(future
(println (resolve 'b)) ; #'user/b (shouldn't it be still undefined at this point?)
(def b "b")
(println (resolve 'b))) ; #'user/b
我也想知道这是否是一个合适的解决方案(不是完全解决同一个问题,而是在我的环境中做同等的工作):
(def c (atom nil))
(future
(println @c) ; nil
(reset! c "c")
(println @c)) ; c
这种行为是def
表单编译方式的结果。
请注意,在任何情况下,使用不在顶层的def
表单(或者可能在顶层let
内部——请参阅下面关于本例的更多评论)都是不受欢迎的风格问题。另一方面,使用Atom的代码片段是很好的——如果它能满足您的需要,就没有理由不使用它。
关于def
的故事:
-
def
表格汇编:当遇到
def
表单时,编译器会在当前命名空间中创建适当名称的Var。(通过使用命名空间限定符号作为def
的名称参数,尝试在当前命名空间之外def
Var会导致异常)。Var最初是未绑定的并且保持未绑定直到def
被实际执行;对于顶级def
,这将是立即的,但对于隐藏在函数体中(或let
表单中——见下文)的def
,这将在调用函数时发生:;;; 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
-
第一个例子——
do
形式:顶级
do
被视为其内容在发生do
的地方被拼接到代码流中。因此,如果在REPL中键入(do (println ...) (def ...) (println ...))
,相当于键入第一个println
表达式,然后键入def
,然后键入第二个println
表达式(除了REPL只生成一个新提示)。 -
第二个例子——使用
future
:CCD_ 21扩展到接近CCD_。如果
...
包含一个def
表单,它将按照我们上面看到的方式进行编译。当匿名函数在自己的线程上执行时,Var将已经创建,因此resolve
将能够找到它。 -
顺便说一句,让我们看看类似的片段及其输出:
(let [] (println (resolve 'c)) (def c "c") (println (resolve 'c))) ; #'user/c ; #'user/c ; => nil
原因和以前一样,还有一点是
let
首先被编译,然后作为一个整体执行。当使用内部有定义的顶级let
表单时,应该记住这一点——只要定义中没有任何副作用代码,通常是可以的;否则必须格外小心。