最近我发现在宏中使用 eval,我知道这有点失礼,但我们暂时忽略它。让我感到惊讶的是,eval
能够在宏扩展时解析全局变量。下面是一个人为的例子,只是为了说明我所指的情况:
(def list-of-things (range 10))
(defmacro force-eval [args]
(apply + (eval args)))
(macroexpand-1 '(force-eval list-of-things))
; => 45
我希望 args
解析为 force-eval
中的符号 list-of-things
,然后是 list -of-things
被评估导致错误,因为它是未绑定(bind)的:
“在此上下文中无法解析符号事物列表”
但是,list-of-things
被解析为 (range 10)
并且没有抛出错误 - 宏扩展成功。
将此与尝试执行相同的宏扩展进行对比,但在本地绑定(bind)上下文中:
(defmacro force-eval [args]
(apply + (eval args)))
(let [list-of-things (range 10)]
(macroexpand-1 '(force-eval list-of-things)))
; => Unable to resolve symbol: list-of-thingss in this context
请注意,在上面的示例中,我假设 list-of-things
之前未绑定(bind),例如一个新的 REPL。最后一个例子说明了为什么这很重要:
(defmacro force-eval [args]
(apply + (eval args)))
(def list-of-things (range 10 20))
(let [list-of-thing (range 10)]
(macroexpand-1 '(force-eval list-of-things)))
; => 145
上面的例子表明局部变量被忽略了,这是 eval
的预期行为,但是当您期望全局变量在宏扩展时也不可用时会有点困惑。
我似乎对宏展开时究竟可用什么有误解。我以前认为基本上任何绑定(bind),无论是全局的还是本地的,在运行时之前都是不可用的。显然这是一个错误的假设。 我的困惑的答案仅仅是全局变量在宏扩展时可用吗?还是我在这里遗漏了一些细微差别?
注意:this related post closely 描述了一个类似的问题,但重点更多地放在如何避免不恰本地使用 eval
上。我主要想了解为什么 eval
在第一个示例中起作用,以及扩展在宏扩展时 eval 可用的内容。
最佳答案
当然,vars 必须在编译时可见。这就是存储 first
和 +
等函数的地方。没有它们,您将无能为力。
但请记住,您必须确保正确引用它们。在 repl 中,*ns*
将被绑定(bind),因此对符号的引用将在当前命名空间中查找。如果您通过 -main
而不是 repl 运行程序,*ns*
将不会被绑定(bind),并且只会找到适当限定的变量。您可以确保使用
`(force-eval list-of-things)
代替
'(force-eval list-of-things)
注意我不区分全局变量和非全局变量。 Clojure 中的所有变量都是全局变量。本地绑定(bind)不称为变量。它们被称为局部变量、绑定(bind)、变量或这些词的某种组合。
关于clojure - 在宏中使用 eval(全局变量意外解析),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68433896/