Macros 宏生成从另一个命名空间调用的宏::Can';t指不';不存在
我有一个生成宏的宏,我试图从另一个名称空间调用它,但它失败了,“不能引用不存在的限定变量” 我设法在下面的代码中重现了它,这是最简单的例子。我还找到了一个解决方法,但是我想了解问题的原因,以及是否存在更好的解决方案 问题 文件foo.clj 文件boo.clj 使用以下命令执行上述代码时:Macros 宏生成从另一个命名空间调用的宏::Can';t指不';不存在,macros,clojure,Macros,Clojure,我有一个生成宏的宏,我试图从另一个名称空间调用它,但它失败了,“不能引用不存在的限定变量” 我设法在下面的代码中重现了它,这是最简单的例子。我还找到了一个解决方法,但是我想了解问题的原因,以及是否存在更好的解决方案 问题 文件foo.clj 文件boo.clj 使用以下命令执行上述代码时: java -cp clojure-1.4.0.jar clojure.main boo.clj 。。。在以下情况下失败: Exception in thread "main" java.lang.Runti
java -cp clojure-1.4.0.jar clojure.main boo.clj
。。。在以下情况下失败:
Exception in thread "main" java.lang.RuntimeException: Can't refer to qualified var that doesn't exist, compiling:(...boo.clj:2)
变通办法
由于某些原因,当生成宏的宏被增强为接受要创建的宏的名称作为参数时,不会出现故障
文件foo.clj
文件boo.clj
如上所述运行文件boo.clj在控制台上输出一个干净的“1”,没有任何抱怨
那么,在第一种情况下出现了什么问题?是否有其他方法来修复它?更改宏生成宏以接受将生成的宏的名称作为参数?另外,当从同一名称空间调用生成宏的宏时,为什么不失败?如果希望宏将符号引入运行它的名称空间,而不是写入它的名称空间,您可以使用
unquote
和quote
的组合让defmacro在宏扩展时生成一个普通的非限定符号
(ns foo)
(defmacro create-my-macro []
`(defmacro ~'my-macro []
nil))
boo> (my-macro)
nil
调用(symbol macroName)
通过从字符串创建一个非名称空间限定的符号来完成大致相同的事情。您可以在第一个示例中使用相同的表单:
(defmacro create-my-macro []
`(defmacro ~(symbol "my-macro") []
"new-result"))
boo> (my-macro)
"new-result"
“那么,第一种情况下出现了什么问题?有没有其他方法可以修复它?更改宏生成宏以接受将生成的宏的名称作为参数?”
错误的是,宏试图执行一种称为“符号捕获”的操作:它试图定义一个符号,该符号可能最终会覆盖目标命名空间中已经存在的符号,而clojure试图保护您免受与符号捕获相关的bug的影响
如果您确信您需要的是符号捕获,那么按照Arthur Ulfeldt的建议进行操作就是您需要的(使用unquote-quote组合~'my-macro)
但我建议您使用初始解决方案的一个变体,并明确您的宏将在当前名称空间中定义一个变量:
(ns foo)
(defmacro create-my-macro [macroName]
`(defmacro ~macroName [] `1))
对宏的调用如下所示:
(create-my-macro mymacro)
这将创建一个名为“mymacro”的宏,然后可以按如下方式调用该宏:
(mymacro) ;; would return 1
“另外,当从同一名称空间调用生成宏的宏时,为什么不失败?”
对此不太确定,但我的猜测是,当您在宏所在的同一命名空间中定义符号时,假定您知道哪些符号已经在使用,并且负责不覆盖(捕获)无意中使用的符号。然而,在从不同名称空间调用的情况下,符号捕获(如果允许的话)将对您产生令人惊讶的副作用。再说一遍,这只是我的猜测
(defmacro create-my-macro []
`(defmacro ~(symbol "my-macro") []
"new-result"))
boo> (my-macro)
"new-result"
(ns foo)
(defmacro create-my-macro [macroName]
`(defmacro ~macroName [] `1))
(create-my-macro mymacro)
(mymacro) ;; would return 1