2024 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅关于页面以了解更多关于如何使用本站的信息。

0
编译器
被重新标记

我明白以下用法有些不寻常,但当我尝试将变量变为动态时,我看到了一些奇怪的行为差异。如果我没有工作,我都不会感到惊讶,但是我真没想到它不会是一致的。

(def v :init)

(do
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :new ; the value is changed as expected

(let []
  (.setDynamic #'v true)
  (binding [v :new]
    (println v))
  (.setDynamic #'v false))
=> :init ; the code is the same as above except for let

在深入编译器后,我发现它在 ObjExpr.emitVarValue 中认为 vlet 示例中不是动态的,因此返回根值。

有趣的是,如果我用 (println @#'v) 这样的方式取消引用变量,这两种情况都会返回 :new 绑定。

2 个答案

+3

被选中
 
最佳答案

这是 let 和 do 之间的区别:对于顶层 do 表达式,主体被提升并进行编译和执行,每次只处理一个。

因此对于

(do
  (.setDynamic #'v true) ;1
  (binding [v :new] ;2
    (println v))
  (.setDynamic #'v false) ; 3
 )

表达式被分为三个表达式

(.setDynamic #'v true) ; 1

.

(binding [v :new] ; 2
    (println v))

.

(.setDynamic #'v false) ;3

然后每个表达式依次编译和执行。这意味着在编译表达式 #2 时,表达式 #1 的执行效果是可见的。

变量的动态检查发生在编译时,而不是运行时,因此表达式 #1 的运行意味着表达式 #2 中对 v 的引用将以不同的方式编译,以处理可能的重新绑定的变量。

对于 let 表达式,整个表达式会一次性编译和执行。因此当 #2 编译时,#1 尚未执行,所以变量不会被标记为动态,因此编译器不会生成处理已绑定变量的代码。

+2

Var.setDynamic() 不是 Clojure 的公共 API 的一部分,所以我不知道这个问题是否相关。

是的,这正是我猜测的。

我正在进行实验,使用 `binding` 而不是最初声明变量为动态的(因为我只想在测试期间更改函数,而不会在实际代码中更改),遇到了 .setDynamic(),因此我想尝试一下。
...