Clojure线程环境中的全局变量行为

时间:2012-08-21 17:22:39

标签: clojure global-variables

鉴于这是有道理的,我希望:

(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

1 个答案:

答案 0 :(得分:7)

这种行为是由于编译def表单的方式而产生的。

请注意,使用def表单不在顶级(或者可能在顶级let内 - 请参阅下面有关此案例的更多评论)作为一种风格问题不赞成任何情况。另一方面,使用Atom的片段很好 - 没有理由不使用它,如果它能达到你想要的效果。

转到def故事:

  1. 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
    
  2. 第一个示例 - 使用do形式:

    顶层do被视为其内容被拼接到do发生的地方的代码流中。因此,如果您在REPL中键入(do (println ...) (def ...) (println ...)),则相当于键入第一个println表达式,然后键入def,然后键入第二个println表达式(除了REPL仅生成一个新的提示)。

  3. 第二个例子 - future

    (future ...)扩展为接近(future-call (fn [] ...))的内容。如果...包含def表单,则会以我们上面看到的方式编译。当匿名函数在其自己的线程上执行时,Var将被创建,因此resolve将能够找到它。

  4. 作为旁注,我们来看一下类似的片段及其输出:

    (let []
      (println (resolve 'c)) 
      (def c "c") 
      (println (resolve 'c)))
    ; #'user/c
    ; #'user/c
    ; => nil
    

    原因与以前一样,加上let首先被编译,然后作为一个整体执行。当使用带有定义的顶级let表单时,应该记住这一点 - 只要没有副作用代码与定义混合,通常就可以了。否则必须格外小心。