如何在`defmacro`中正确使用syntax-quote和unquote

时间:2014-06-22 22:32:32

标签: macros clojure

我有一个简单的宏:

(defmacro macrotest [coll]
  `(let [result# ~(reduce + coll)]
         result#))

为什么,如果此代码有效:

(macrotest [1 2 3])

这段代码不起作用吗?

(def mycoll [1 2 3])
(macrotest mycoll)

1 个答案:

答案 0 :(得分:10)

符号与其指向的值不同。

关于宏的一个重要考虑因素不仅仅是它们如何创建新代码,还包括它们如何处理您传入的参数。

考虑一下:

(def v [1 2 3])

(somefunction v)

传递给此函数的参数v不会作为符号v到达函数内部。相反,因为这是一个函数调用,所以首先计算参数,然后将它们的结果值传递给函数。因此该函数将显示[1 2 3]

但宏不是这样的,虽然很容易忘记。当你打电话:

(somemacro v)

v 已评估,传递[1 2 3]。在宏内部,你得到的只是一个符号v。现在,您的宏可以在宏创建的代码中发出您传入的符号,但是除非您使用{添加额外级别的评估,否则它不能对v执行任何操作。 {1}}(见脚注 - 不推荐)。

当你取消引用宏参数时,你会得到一个符号,而不是一个值。

考虑这个例子:

eval

这就像在REPL上尝试这样做:user> (def a 5) #'user/a user> (defmacro m [x] `(+ ~x ~x)) #'user/m user> (m a) 10 user> (macroexpand '(m a)) (clojure.core/+ a a) user> (defmacro m [x] `~(+ x x)) #'user/m user> (m a) ClassCastException clojure.lang.Symbol cannot be cast to java.lang.Number clojure.lang.Numbers.add (Numbers.java:126) 添加符号,而不是添加符号所指的值。

您可以查看宏(+ 'a 'a)的这两个不同定义,并认为它们正在做同样的事情。但是在第二个中,尝试使用符号 m,以便对其进行添加。在第一个宏中,不要求编译器对x做任何事情。它只是被告知发出加法操作,然后在运行时实际处理。

请记住,宏的一点是创建代码不执行函数可以执行的运行时活动。然后立即运行创建的代码(通过隐式x),因此很容易忘记这种分离,但它们是两个不同的步骤。

作为最后的练习,假装你是编译器。我希望您现在告诉我 此代码的结果值是什么:

eval

好?这是不可能的。您无法告诉我这个问题的答案,因为您不知道(* t w)t是什么。但是,稍后,在运行时,当这些可能具有值时,那么它将很容易。

这就是宏所做的事情:它们输出运行时要处理的内容。


(非常不推荐,但为了进一步描述发生了什么,这有效:)

<子>使用者&gt; (def a 1)

<子>使用者&gt; (defmacro m [x]`〜(+(eval x)(eval x)))

<子>使用者&gt; (m a)

<子> 2