你可能会问:为何我们要大费周章地先为宏计算出展开式,再对展开式求值?为何不让宏体直接生成期望的结果?这背后的原因与编译机制有关。
当待编译的 Lisp 程序中出现宏调用时,Lisp 编译器会像解释器一样调用该宏的定义,并得到对应的展开式。但编译器并不会直接求值这个展开式,而是将其当作直接写在程序里的代码来编译。最终,编译后的代码既能实现宏预期的返回值和副作用,又能以编译代码的完整速度执行。如果让宏体自身直接计算返回值和副作用,这一机制就无法生效 —— 因为这些计算会在编译阶段完成,而这通常没有实际意义。
要让宏调用的编译正常工作,在编译这些宏调用时,对应的宏必须已在 Lisp 中完成定义。编译器提供了一项特殊功能来协助实现这一点:若待编译的文件中包含 defmacro 形式,该宏会被临时定义,并在该文件后续的编译过程中生效。
对文件进行字节编译时,还会执行文件中顶层的所有 require 调用。因此,你可以通过加载(require)定义宏的文件,确保编译期间能获取到必要的宏定义(see 功能)。为了避免他人运行编译后的程序时加载这些宏定义文件,可将 require 调用包裹在 eval-when-compile 中(see 编译时求值)。