我有一个简单的宏:
(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)
好?不可能.没有办法,你能告诉我回答这个问题,因为你不知道什么t和w有.但是,稍后,在运行时,当这些可能具有值时,那么它将很容易.
这就是宏所做的:它们输出运行时要处理的东西.
(非常不推荐,但为了进一步描述正在发生的事情,这有效:)
用户>(def a 1)
user>(defmacro m [x]`〜(+(eval x)(eval x)))
用户>(ma)
2