如何在`defmacro`中正确使用语法引用和反引用

5

我有一个简单的宏:

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

为什么这段代码有效:

(macrotest [1 2 3])

这段代码为什么不能工作?

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

3
该减少操作是在编译时发生的,因此当您调用它时,您正在尝试执行(reduce + somesymbol)。您的宏到底要做什么?我猜想您想要的是(reduce + ~coll)。 - Diego Basch
3
宏展开是你的朋友。 - dsm
1个回答

10

符号并不等同于它们所指向的值。

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

请考虑以下内容:

(def v [1 2 3])

(somefunction v)

传递给此函数的参数v不会作为符号v到达函数内部。相反,因为这是一个函数调用,首先对参数进行求值,然后将其结果值传递到函数中。因此,函数将看到[1 2 3]

但是宏不是这样的,尽管很容易忘记。当您调用:

(somemacro v)

v不会被求值,也不会传入[1 2 3]。在宏内部,你得到的只是一个符号v。现在,您的宏可以在它创建的代码中发出您传递的符号,但是除非您使用eval(请参见脚注--不建议使用)添加额外的求值级别,否则它无法处理v的值。

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

请考虑以下示例:

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)

这就像在 REPL 中尝试执行以下命令: (+ 'a 'a),它是将符号相加,而不是将这些符号指向的值相加。
你可能会看到这两个不同的宏定义并认为它们正在做相同的事情。但是在第二个宏定义中,有一个尝试通过符号x进行加法操作。在第一个宏定义中,编译器没有被要求对x进行任何操作,它只是被告知需要输出加法运算,这将在运行时实际处理。
请记住,宏的作用是创建代码而不是执行函数所需的运行时活动。所创建的代码随即立刻被执行(通过隐式的eval),所以很容易忽略这种分离,但它们是两个不同的步骤。
最后,假设您是一个编译器。我想让您立即告诉我此代码的结果值: (* t w) 好的?这是不可能的。您无法回答这个问题,因为您不知道tw是什么。然而,在稍后运行时,当它们预计具有值时,这将变得容易。
这就是宏所做的:它们输出供运行时处理的内容。
(非常不建议,但为了进一步描述正在发生的情况,这也可以运行:) user> (def a 1) user> (defmacro m [x] `~(+ (eval x) (eval x))) user> (m a) 2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接