鉴于这是有道理的,我希望:
(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
答案 0 :(得分:7)
这种行为是由于编译def
表单的方式而产生的。
请注意,使用def
表单不在顶级(或者可能在顶级let
内 - 请参阅下面有关此案例的更多评论)作为一种风格问题不赞成任何情况。另一方面,使用Atom的片段很好 - 没有理由不使用它,如果它能达到你想要的效果。
转到def
故事:
def
表格的汇编:
当遇到def
表单时,当前命名空间中的编译器会在当时创建相应名称的Var 。 (通过使用名称空间限定符号作为def
的名称参数,尝试def
当前名称空间之外的Var会导致异常)。 Var最初是未绑定的,并且在实际执行def
之前保持不绑定状态;对于顶级def
,它会马上出现,但对于隐藏在函数体内(或def
形式内部的let
- 见下文),当函数被调用时:
;;; 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
:
(future ...)
扩展为接近(future-call (fn [] ...))
的内容。如果...
包含def
表单,则会以我们上面看到的方式编译。当匿名函数在其自己的线程上执行时,Var将被创建,因此resolve
将能够找到它。
作为旁注,我们来看一下类似的片段及其输出:
(let []
(println (resolve 'c))
(def c "c")
(println (resolve 'c)))
; #'user/c
; #'user/c
; => nil
原因与以前一样,加上let
首先被编译,然后作为一个整体执行。当使用带有定义的顶级let
表单时,应该记住这一点 - 只要没有副作用代码与定义混合,通常就可以了。否则必须格外小心。