我注意到,当eval
在宏扩展时被调用,并且它试图评估一个在被评估代码中没有命名特殊形式或局部绑定但在宏扩展代码中命名局部绑定的符号时,即使该符号还命名了全局变量,也会引发异常。
以下是这种行为和其他或多或少相关的行为的一些例子:
(def x 0)
(defmacro m0 [] (eval 'x))
(let [x 1] (m0))
;=> CompilerException java.lang.UnsupportedOperationException: Can't eval locals
(let [y 1] (m0))
;=> 0
(defmacro m1 [] (eval '(inc x)))
(let [x 1] (m1))
;=> CompilerException java.lang.InstantiationException: user$eval572$eval573__574
(let [y 1] (m1))
;=> 1
(defmacro m2 [] (eval '(let [x 2] x)))
(let [x 1] (m2))
;=> 2
(defmacro m3 [] (load-string "x"))
(let [x 1] (m3))
;=> 0
(defmacro m4 [] x)
(let [x 1] (m4))
;=> 0
(let [x 1] (eval 'x))
;=> 0
我在这里找到了关于这个问题的简短讨论。我认为,根据这种评估的描述,在这种情况下,符号应该评估到全局变量的内容。事实上,如果不是Clojure内置的宏扩展机制,而是使用一些macroexpand-all
函数(比如clojure.tools.analyzer.jvm
中的函数,这是我所知道的最准确的函数(,就会发生这种情况:
(require '[clojure.tools.analyzer.jvm :refer [macroexpand-all]])
(macroexpand-all '(let [x 1] (m0)))
;=> (let* [x 1] 0)
;Here is a workaround:
(defmacro mexa [form] (macroexpand-all form))
(mexa (let [x 1] (m0)))
;=> 0
那么这应该被认为是一个bug吗?如果没有,理由是什么?难道不应该有一些关于这种行为的文档和解释吗;有问题的";符号以复杂的形式出现?
我认为所有这些都与您链接的源代码一致。在这两种失败的情况下,存在一个名为x
的局部,因此当eval
评估表单时,x解析为一个局部。由于它已经解析为local,因此它不会继续寻找同名的var来解析。解析后会进行评估,但不允许对local进行评估,从而导致异常。没有回溯来寻找合适的var。
当然,我同意,一个更好的错误消息会很好。但是eval
在普通的Clojure代码中使用不多,所以我对利基案例中的失败没有像可能的那样得到很好的记录并不感到惊讶