Macro Expansion Time vs. Runtime(宏展开期和运行期)
The key to understanding macros is to be quite clear about the distinction between the code that generates code (macros) and the code that eventually makes up the program (everything else). When you write macros, you’re writing programs that will be used by the compiler to generate the code that will then be compiled. Only after all the macros have been fully expanded and the resulting code compiled can the program actually be run. The time when macros run is called macro expansion time; this is distinct from runtime, when regular code, including the code generated by macros, runs.
理解宏的关键在于必须清楚地知道那些生成代码的代码(宏)和那些最终构成程序的代码(所有其他内容)之间的区别。当编写宏时,你是在编写那些将被编译器用来生成代码并随后编译的程序。只有当所有的宏都被完全展开并且产生的代码被编译后,程序才可以实际运行。宏运行的时期被称为宏展开期(macro expansion time),这和运行期(runtime)是不同的,后者是正常的代码(包括那些由宏所生成的代码)实际运行的阶段。
It’s important to keep this distinction firmly in mind because code running at macro expansion time runs in a very different environment than code running at runtime. Namely, at macro expansion time, there’s no way to access the data that will exist at runtime. Like Mac, who couldn’t run the programs he was working on because he didn’t know what the correct inputs were, code running at macro expansion time can deal only with the data that’s inherent in the source code. For instance, suppose the following source code appears somewhere in a program:
牢记这一区别很重要,因为运行在宏展开期的代码与那些运行在运行期的代码相比,它们的运行环境完全不同。也就是说,在宏展开期无法访问那些仅存在于运行期的数据。正如 Mac 无法运行他写的程序是因为不知道正确的输入那样,运行在宏展开期的代码也只能处理那些来自源代码本身的数据。例如,假设在程序的某个地方出现了下面这样的源代码:
(defun foo (x)
(when (> x 10) (print 'big)))
Normally you’d think of x
as a variable that will hold the argument passed in a call to foo. But at macro expansion time, such as when the compiler is running the WHEN macro, the only data available is the source code. Since the program isn’t running yet, there’s no call to foo and thus no value associated with x. Instead, the values the compiler passes to WHEN are the Lisp lists representing the source code, namely, (> x 10)
and (print 'big)
. Suppose that WHEN is defined, as you saw in the previous chapter, with something like the following macro:
正常情况下,你将 x
设想为一个变量,用它保存传递给一个对 foo
调用的实参。但在宏展开期,比如说当编译器正在运行 WHEN 宏的时候,唯一可用的数据就是源代码。由于程序尚未运行,没有对 foo
的调用,因此也没有值关联到 x
上。相反,编译器传递给 WHEN 的值只是代表源代码的Lisp列表,也即 (> x 10)
以及 (print 'big)
。 假设 WHEN 确如前一章中所见的那样用类似下面的宏定义而成:
(defmacro when (condition &rest body)
`(if ,condition (progn ,@body)))
When the code in foo is compiled, the WHEN macro will be run with those two forms as arguments. The parameter condition
will be bound to the form (> x 10)
, and the form (print 'big)
will be collected into a list that will become the value of the &rest
body parameter. The backquote expression will then generate this code:
当 foo
中的代码被编译时,WHEN 宏将以那两个形式作为实参来运行。形参 condition
会被绑定为 (> x 10)
, 而 (print 'big)
作为 &rest body
形参的值会被收集到一个列表中。那个反引用表达式将随后通过插入 condition
的值,将 body
的值嵌入 PROGN 的主体来生成下面的代码:
(if (> x 10) (progn (print 'big)))
by interpolating in the value of condition and splicing the value of body into the PROGN.
When Lisp is interpreted, rather than compiled, the distinction between macro expansion time and runtime is less clear because they’re temporally intertwined. Also, the language standard doesn’t specify exactly how an interpreter must handle macros—it could expand all the macros in the form being interpreted and then interpret the resulting code, or it could start right in on interpreting the form and expand macros when it hits them. In either case, macros are always passed the unevaluated Lisp objects representing the subforms of the macro form, and the job of the macro is still to produce code that will do something rather than to do anything directly.
当 Lisp 被解释而非编译执行时,宏展开期和运行期之间的区别不甚明显,因为它们临时纠缠在了一起。同样,语言标准并未规定解释器处理宏的具体方式——它可能在被解释的形式中展开所有的宏,然后解释执行那些宏所生成的代码,也可能是直接解释一个形式并在每次遇到宏的时候才展开。无论哪种情况,总是向宏传递那些代表宏形式中子形式的未经求值的 Lisp 对象,并且宏的作用仍然是生成做某些事情的代码,而非直接做任何事情。