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

Rib*_*elo 5 macros clojure

我有一个简单的宏:

(defmacro macrotest [coll]
  `(let [result# ~(reduce + coll)]
         result#))
Run Code Online (Sandbox Code Playgroud)

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

(macrotest [1 2 3])
Run Code Online (Sandbox Code Playgroud)

这段代码不起作用吗?

(def mycoll [1 2 3])
(macrotest mycoll)
Run Code Online (Sandbox Code Playgroud)

joh*_*ers 10

符号与它们指向的值不同.

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

考虑一下:

(def v [1 2 3])

(somefunction v)

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

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

(somemacro v)

v没有评估,并没有传递[1 2 3].在宏中,你得到的只是一个符号v.现在,您的宏可以在宏创建的代码中发出您传入的符号,但除非您使用添加额外级别的评估,否则它无法对进行任何v操作eval(请参阅脚注 - 不推荐).

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

考虑这个例子:

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)
Run Code Online (Sandbox Code Playgroud)

这就像尝试在REPL中执行此操作:(+ 'a 'a)添加符号,而不是添加这些符号指向的值.

你可能会看看这两个不同的宏定义,m并认为它们正在做同样的事情.但是在第二个中,有一个尝试用符号一些事情,以及添加它.在第一个宏中,不要求编译器对x做任何事情.它只是被告知发出加法操作,然后在运行时实际处理. x

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

作为最后的练习,假装你是一个编译器.我希望你现在告诉我这段代码的结果值是什么:

(* t w)

好?不可能.没有办法,你能告诉我回答这个问题,因为你不知道什么tw有.但是,稍后,在运行时,当这些可能具有值时,那么它将很容易.

这就是宏所做的:它们输出运行时要处理的东西.


(非常不推荐,但为了进一步描述正在发生的事情,这有效:)

用户>(def a 1)

user>(defmacro m [x]`〜(+(eval x)(eval x)))

用户>(ma)

2