【问题标题】:macro-producing macro invoked from another namespace :: Can't refer to qualified var that doesn't exist从另一个命名空间调用的宏生成宏 :: 不能引用不存在的限定变量
【发布时间】:2013-02-27 18:19:00
【问题描述】:

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

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

问题

文件 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”,没有任何抱怨。

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

【问题讨论】:

    标签: macros clojure


    【解决方案1】:

    如果您希望宏将符号引入运行它的命名空间,而不是写入它的命名空间,您可以使用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"
    

    【讨论】:

      【解决方案2】:

      '那么,第一种情况出了什么问题,是否有另一种方法可以修复它,更改宏生成宏以接受要生成的宏的名称作为参数?'

      错误是宏试图做一些叫做“符号捕获”的事情:它试图定义一个符号,最终可能会覆盖目标命名空间中已经存在的符号,而 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
      

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-01-31
        • 1970-01-01
        • 2014-12-10
        • 1970-01-01
        • 2019-02-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多