3

我有一个宏生成宏,我试图从另一个命名空间调用它,但它失败并显示“无法引用不存在的合格变量”。

我设法在下面的代码中重现它,这是最简单的例子,可以说明问题。我还找到了一种解决方法,但是我想了解问题的原因以及是否存在更好的解决方案。

问题

文件 foo.clj

(ns foo)
(defmacro create-my-macro []
  `(defmacro my-macro []
      nil))

文件 boo.clj

(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)

解决方法

出于某种原因,当宏生成宏被增强以接受要创建的宏的名称作为参数时,没有失败。

文件 foo.clj

(ns foo)
(defmacro create-my-macro [macroName]
  (let [the-macroName (symbol macroName)]
    `(defmacro ~the-macroName []
         1)))

文件 boo.clj

(ns boo (:use [foo]))
(create-my-macro "foo")
(println (foo))

如上所述运行文件 boo.clj 在控制台上输出一个干净的“1”,没有任何抱怨。

那么,在第一种情况下出了什么问题,是否有另一种方法可以修复它,更改宏生成宏以接受要生成的宏的名称作为参数?另外,当从同一个命名空间调用宏生成宏时,为什么它不会失败?

4

2 回答 2

4

如果您希望宏将符号引入运行它的命名空间,而不是写入它的命名空间,您可以使用 和 的组合unquotequote获取 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"
于 2013-02-27T19:19:06.803 回答
1

“那么,第一种情况出了什么问题,是否有另一种方法来解决它,改变宏生成宏以接受要生成的宏的名称作为参数?”

错误是宏正试图做一些称为“符号捕获”的事情:它试图定义一个可能最终覆盖目标命名空间中已经存在的符号的符号,而 clojure 试图保护您免受相关的错误的影响带符号捕获。

如果您确信您需要的是符号捕获,那么执行 Arthur Ulfeldt 上面的建议就是您所需要的(使用 unquote 引用组合 ~'my-macro)

但我对您的建议是使用初始解决方案的变体,并明确说明您的宏将在当前命名空间中定义一个变量:

(ns foo)
(defmacro create-my-macro [macroName]
  `(defmacro ~macroName [] `1))

对您的宏的调用如下所示:

(create-my-macro mymacro)

这将创建一个名为“mymacro”的宏,然后可以按如下方式调用它:

(mymacro)   ;; would return 1

“另外,为什么从同一个命名空间调用宏生成宏时它不会失败?”

不确定这一点,但我的猜测是,当您在存在宏的同一名称空间中定义符号时,假设您知道哪些符号已在使用,并且负责不覆盖(捕获)已无意中使用的符号. 而在从不同名称空间调用的情况下,符号捕获(如果允许)对您来说将是一个令人惊讶的副作用。同样,这只是我的猜测。

于 2013-02-27T21:23:44.913 回答