10.2 宏 (Macros)
写出能写程序的程序的最普遍方法是通过定义宏。宏是通过转换 (transformation)而实现的操作符。你通过说明你一个调用应该要翻译成什么,来定义一个宏。这个翻译称为宏展开(macro-expansion),宏展开由编译器自动完成。所以宏所产生的代码,会变成程序的一个部分,就像你自己输入的程序一样。
宏通常通过调用 defmacro
来定义。一个 defmacro
看起来很像 defun
。但是与其定义一个函数调用应该产生的值,它定义了该怎么翻译出一个函数调用。举例来说,一个将其参数设为 nil
的宏可以定义成如下:
(defmacro nil! (x)
(list 'setf x nil))
这定义了一个新的操作符,称为 nil!
,它接受一个参数。一个这样形式 (nil! a)
的调用,会在求值或编译前,被翻译成 (setf a nil)
。所以如果我们输入 (nil! x)
至顶层,
> (nil! x)
NIL
> x
NIL
完全等同于输入表达式 (setf x nil)
。
要测试一个函数,我们调用它,但要测试一个宏,我们看它的展开式 (expansion)。
函数 macroexpand-1
接受一个宏调用,并产生它的展开式:
> (macroexpand-1 '(nil! x))
(SETF X NIL)
T
一个宏调用可以展开成另一个宏调用。当编译器(或顶层)遇到一个宏调用时,它持续展开它,直到不可展开为止。
理解宏的秘密是理解它们是如何被实现的。在台面底下,它们只是转换成表达式的函数。举例来说,如果你传入这个形式 (nil! a)
的表达式给这个函数
(lambda (expr)
(apply #'(lambda (x) (list 'setf x nil))
(cdr expr)))
它会返回 (setf a nil)
。当你使用 defmacro
,你定义一个类似这样的函数。 macroexpand-1
全部所做的事情是,当它看到一个表达式的 car
是宏时,将表达式传给对应的函数。