我有一个简单的宏:
(defmacro macrotest [coll]
`(let [result# ~(reduce + coll)]
result#))
为什么,如果此代码有效:
(macrotest [1 2 3])
这段代码不起作用吗?
(def mycoll [1 2 3])
(macrotest mycoll)
答案 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 子>