在线程环境中克隆全局变量行为



如果这能像我预期的那样工作:

(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的故事:

  1. 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
    
  2. 第一个例子——do形式:

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

  3. 第二个例子——使用future:

    CCD_ 21扩展到接近CCD_。如果...包含一个def表单,它将按照我们上面看到的方式进行编译。当匿名函数在自己的线程上执行时,Var将已经创建,因此resolve将能够找到它。

  4. 顺便说一句,让我们看看类似的片段及其输出:

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

    原因和以前一样,还有一点是let首先被编译,然后作为一个整体执行。当使用内部有定义的顶级let表单时,应该记住这一点——只要定义中没有任何副作用代码,通常是可以的;否则必须格外小心。

相关内容

  • 没有找到相关文章

最新更新