我有一个宏生成宏,我试图从另一个命名空间调用它,它失败了“无法引用不存在的合格var”。
我设法在下面的代码中重现它,这是解决问题的最简单的代码。我也找到了解决方法,但是我想了解问题的原因以及是否存在更好的解决方案。
(ns foo)
(defmacro create-my-macro []
`(defmacro my-macro []
nil))
(ns boo (:use [foo]))
(create-my-macro)
执行上述代码时使用:
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)
出于某种原因,当增强宏生成宏以接受要作为参数创建的宏的名称时,没有失败。
(ns foo)
(defmacro create-my-macro [macroName]
(let [the-macroName (symbol macroName)]
`(defmacro ~the-macroName []
1)))
(ns boo (:use [foo]))
(create-my-macro "foo")
(println (foo))
如上所述运行文件boo.clj在控制台上输出一个干净的“1”,没有任何抱怨。
那么,在第一种情况下出了什么问题,是否有另一种方法来修复它改变宏生成宏以接受作为参数生成的宏的名称?另外,为什么在从同一名称空间调用宏生成宏时它不会失败?
答案 0 :(得分:4)
如果您希望宏将符号引入运行它的命名空间,而不是写入它的命名空间,您可以使用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"
答案 1 :(得分:1)
'那么,在第一种情况下出了什么问题,还有另一种方法来修复它,更改宏生成宏以接受要作为参数生成的宏的名称?' 强> 的
错误在于宏正在尝试做一些名为“符号捕获”的事情:它试图定义一个符号,该符号可能最终覆盖目标命名空间中已存在的符号,而clojure是试图保护你免受与符号捕获相关的错误的影响。
如果您确信您需要的是符号捕获,那么按照Arthur Ulfeldt建议的那样是您需要的(使用非引号引用组合〜' my-macro)
但我建议您使用初始解决方案的变体并明确表示您的宏将在当前命名空间中定义变量:
(ns foo)
(defmacro create-my-macro [macroName]
`(defmacro ~macroName [] `1))
对宏的调用如下所示:
(create-my-macro mymacro)
会创建一个名为' mymacro的宏,然后可以按如下方式调用:
(mymacro) ;; would return 1
&#39;此外,为什么在从同一名称空间调用宏生成宏时它不会失败?&#39; < / p>
不确定这个,但我的猜测是,当您在宏存在的同一命名空间中定义符号时,假设您知道哪些符号已被使用并且负责不覆盖(捕获)符号已经无意中使用过了。而在从不同命名空间调用的情况下,符号捕获(如果允许)对您来说将是一个令人惊讶的副作用。再次,这只是我的猜测。