Generating the Expansion(生成展开式)
Because do-primes
is a fairly simple macro, after you’ve destructured the arguments, all that’s left is to interpolate them into a template to get the expansion.
由于 do-primes
是一个相当简单的宏,在解构了参数以后,剩下的就是将它们插入到一个模板中来得到展开式。
For simple macros like do-primes
, the special backquote syntax is perfect. To review, a backquoted expression is similar to a quoted expression except you can “unquote” particular subexpressions by preceding them with a comma, possibly followed by an at (@) sign. Without an at sign, the comma causes the value of the subexpression to be included as is. With an at sign, the value—which must be a list—is “spliced” into the enclosing list.
对于像 do-primes
这样简单的宏,特别的反引用语法刚好合适。回顾一下,反引用表达式与引用表达式很相似,除了可以 “解引用”(unquote)特定的值表达式,即在其前面加上逗号,可能再加一个 “@”。没有这个 “@”,逗号会导致子表达式的值被原样包含。有了 “@”,它作为列表的值可被 “拼接” 到它所在的列表中。
Another useful way to think about the backquote syntax is as a particularly concise way of writing code that generates lists. This way of thinking about it has the benefit of being pretty much exactly what’s happening under the covers—when the reader reads a backquoted expression, it translates it into code that generates the appropriate list structure. For instance, `(,a b)
might be read as (list a 'b)
. The language standard doesn’t specify exactly what code the reader must produce as long as it generates the right list structure.
采用另一种方式也会有助于理解反引用语法,这就是将其视为编写生成列表的代码的一种特别简洁的方式。这种理解方式的优点是可以相当明确地看到其表象之下实际发生的事——当读取器读到一个反引用表达式时,它将其翻译成生成适当列表结构的代码。例如,`(,a b)
可以被读取成 (list a 'b)
。语言标准并未明确指定读取器必须产生怎样的代码,只要它能生成正确的列表结构就可以了。
Table 8-1 (Backquote Examples) shows some examples of backquoted expressions along with equivalent list-building code and the result you’d get if you evaluated either the backquoted expression or the equivalent code.
表 8-1 给出了一些反引用表达式的范例,同时带有与之等价的列表构造代码以及其中任意一种形式的求值结果。
Backquote Syntax | Equivalent List-Building Code | Result |
---|---|---|
| (list ‘a (+ 1 2) ‘c) | (a 3 c) |
| (list ‘a (list 1 2) ‘c) | (a (1 2) c) |
`(a ,@(list 1 2) c) | (append (list ‘a) (list 1 2) (list ‘c)) | (a 1 2 c) |
It’s important to note that backquote is just a convenience. But it’s a big convenience. To appreciate how big, compare the backquoted version of do-primes
to the following version, which uses explicit list-building code:
重要的是要注意反引用只是一种便利措施,但确实相当便利 。为了说明其便利程度,我们可以将 do-primes
的反引用版本和下面的版本作比较,后者使用了显式的列表构造代码:
(defmacro do-primes-a ((var start end) &body body)
(append '(do)
(list (list (list var
(list 'next-prime start)
(list 'next-prime (list '1+ var)))))
(list (list (list '> var end)))
body))
As you’ll see in a moment, the current implementation of do-primes
doesn’t handle certain edge cases correctly. But first you should verify that it at least works for the original example. You can test it in two ways. You can test it indirectly by simply using it—presumably, if the resulting behavior is correct, the expansion is correct. For instance, you can type the original example’s use of do-primes
to the REPL and see that it indeed prints the right series of prime numbers.
稍后即将看到,do-primes
的当前实现并不能正确地处理特定的临界情况,但首先应当确认它至少应能适用于最初的例子。可以用两种方式来测试它。可以简单地通过使用它来间接地测试,也就是说,如果结果的行为是正确的那么展开式很可能就是正确的。例如,可以将 do-primes
最初的用例键入到 REPL中,你会看到它确实打印出了正确的素数序列。
CL-USER> (do-primes (p 0 19) (format t "~d " p))
2 3 5 7 11 13 17 19
NIL
Or you can check the macro directly by looking at the expansion of a particular call. The function MACROEXPAND-1 takes any Lisp expression as an argument and returns the result of doing one level of macro expansion. Because MACROEXPAND-1 is a function, to pass it a literal macro form you must quote it. You can use it to see the expansion of the previous call.
或者也可以通过查看特定调用的展开式来直接检查该宏。函数 MACROEXPAND-1 接受任何 Lisp 表达式作为参数并返回做宏展开一层的结果。由于 MACROEXPAND-1 是一个函数,所以为了传给它一个字面的宏形式,就必须引用它。可以用它来查看前面调用的展开式。
CL-USER> (macroexpand-1 '(do-primes (p 0 19) (format t "~d " p)))
(DO ((P (NEXT-PRIME 0) (NEXT-PRIME (1+ P))))
((> P 19))
(FORMAT T "~d " P))
T
Or, more conveniently, in SLIME you can check a macro’s expansion by placing the cursor on the opening parenthesis of a macro form in your source code and typing C-c RET
to invoke the Emacs function slime-macroexpand-1
, which will pass the macro form to MACROEXPAND-1 and “pretty print” the result in a temporary buffer.
或者,在 SLIME 中也可以更方便地检查一个宏的展开式:将光标放置在源代码中一个宏形式的开放括号上并输入 C-c RET
来调用 Emacs 函数 slime-macroexpand-1
,后者将把宏调用传递到 MACROEXPAND-1 上并将结果 “美化输出” 到一个临时缓冲区上。
However you get to it, you can see that the result of macro expansion is the same as the original handwritten expansion, so it seems that do-primes
works.
无论怎样得到展开式,你都可以看到宏展开的结果和最初的手写展开式是一样的,因此看起来 do-primes
是有效的。