Clojure和^:dynamic

28

我尝试理解动态变量和绑定函数,因此我尝试了这个(Clojure 1.3):

user=> (defn f [] 
           (def ^:dynamic x 5) 
           (defn g [] (println x)) 
           (defn h [] (binding [x 3] (g))) 
           (h))
#'user/f
user=> (f)     
5
nil

有点困惑,我尝试了这段相对简单的代码:

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn g [] (println y))
#'user/g
user=> (defn h [] (binding [y 3] (g)))
#'user/h
user=> (h)
3
nil

这两段代码有什么区别?为什么第二个示例可以工作,而第一个则不行?提示:我刚意识到以下代码可以工作(但还不完全明白原因):

user=> (def ^:dynamic y 5)
#'user/y
user=> (defn f [] (defn g [] (println y)) (defn h [] (binding [y 3] (g))) (h))
#'user/f
user=> (f)
3
nil
user=> 
1个回答

43
当我在Clojure 1.4中运行你的第一个示例时,得到了3作为结果(正如你所期望的)。你是否尝试过在一个新的REPL中运行它?
^:dynamic是对Clojure编译器的指令,用来表示符号(使用def定义)意图进行动态重新绑定(使用binding)。
例如:
(def foo 1)
(binding [foo 2] foo)
=> IllegalStateException Can't dynamically bind non-dynamic var: ...

(def ^:dynamic bar 10)
(binding [bar 20] bar)    ;; dynamically bind bar within the scope of the binding
=> 20
bar                       ;; check underlying value of bar (outside the binding)
=> 10

请注意,binding在调用线程中具有动态作用域 - 在绑定内调用的任何函数将看到修改后的bar值(20),但任何其他线程仍将看到未更改的根值10。
最后还有一些风格要点可能会对您有所帮助:
  • 通常认为在函数中放置defdefn是不好的做法,因为它们会影响封闭命名空间。在函数内部,您应该使用(let [foo bar] ...)
  • 当您想使用binding时,通常应考虑是否可以改用高阶函数来实现相同的结果。 binding在某些情况下很有用,但通常不是传递参数的好方法 - 函数组合通常更好。原因是binding创建了一个隐含的上下文,该上下文对于执行函数是必需的,并且这可能很难进行测试/调试。

我很难理解你何时需要绑定!它看起来与函数式的方式格格不入。我错过了什么吗? - Hendekagon
2
@Hendekagon - 可能值得在 SO 上提出一个单独的问题。但我发现这是一种额外的在调试/在 REPL 中传递上下文的有用方法——如果你以纯函数式的方式做这件事,那么你需要把新参数贯穿整个(可能非常长的)调用图。 - mikera
我在1.4中尝试了这个,它能够正常工作(如预期),然后在1.3中重试,但失败了。我只能得出结论:1.3中存在一些已经修复的错误。 - Kevin
1
@Kevin ^:dynamic 在1.3版本之前还不是语言的一部分 - 所以不,这并不是一个错误,只是尚不存在的东西;在那个时候,动态变量被命名为*barbells*,这不仅仅是一种约定。 - Charles Duffy
1
抱歉打扰了这个帖子,但绑定在代码中创建沙盒非常有用,只需在特定上下文中绑定变量即可。我已经使用它来允许测试/开发环境中的数据修改,但在生产环境中禁止此行为。此外,如果您查看SICP中Scheme编译器的构建方式,您将看到环境随着每个(大多数)函数评估而传递。同样,绑定可以用于代码隔离、数据管理或类似于连续体的方式使用。 - Travis Rodman
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接