Macro-Writing Macros(生成宏的宏)

    当然,没有理由表明只有在编写函数的时候才能利用宏的优势。宏的作用是将常见的句法模式抽象掉,而反复出现在宏的编写中的特定模式同样也可受益于其抽象能力。

    In fact, you’ve already seen one such pattern—many macros will, like the last version of , start with a LET that introduces a few variables holding gensymed symbols to be used in the macro’s expansion. Since this is such a common pattern, why not abstract it away with its own macro?

    事实上,你已经见过了这样一种模式。许多宏,例如最后版本的 do-primes,它们都以一个 LET 形式开始,后者引入了一些变量用来保存宏展开过程中用到的生成符号。由于这也是一个常见模式,那为什么不用一个宏来将其抽象掉呢?

    In this section you’ll write a macro, with-gensyms, that does just that. In other words, you’ll write a macro-writing macro: a macro that generates code that generates code. While complex macro-writing macros can be a bit confusing until you get used to keeping the various levels of code clear in your mind, with-gensyms is fairly straightforward and will serve as a useful but not too strenuous mental limbering exercise.

    本节将编写一个宏 with-gensyms,它刚好做到这点。换句话说,你将编写一个用来编写宏的宏:一个宏用来生成代码,其代码又生成另外的代码。尽管在你习惯于在头脑中牢记不同层次的代码之前,可能会对复杂的编写宏的宏有一点困惑,但 with-gensyms 是相当简单的,而且还当作可以一个有用但又不会过于浪费脑筋的练习。

    所写的宏应当类似于下面这种形式:

    and have it be equivalent to the previous version of do-primes. In other words, the with-gensyms needs to expand into a LET that binds each named variable, ending-value-name in this case, to a gensymed symbol. That’s easy enough to write with a simple backquote template.

    并且还需要让其等价于之前版本的 do-primes。换句话说,with-gensyms 需要展开成一个 LET,它会把每一个命名的变量(在本例中是 ending-value-name)都绑定到一个生成符号上。很容易就可以写出一个简单的反引用模板。

    Note how you can use a comma to interpolate the value of the LOOP expression. The loop generates a list of binding forms where each binding form consists of a list containing one of the names given to with-gensyms and the literal code (gensym). You can test what code the LOOP expression would generate at the REPL by replacing names with a list of symbols.

    注意你是怎样用一个逗号来插入 LOOP 表达式的值的。这个循环生成了一个绑定形式的列表,其中每个绑定形式由一个含有 中的一个给定名字和字面代码 (gensym) 的列表所构成。你可以通过将 names 替换成一个符号的列表,从而在 REPL 中测试 LOOP 表达式生成的代码。

    在绑定形式的列表之后,with-gensyms 的主体参数被嵌入到LET的主体之中。这样,被封装在一个 with-gensyms 中的代码将可以引用任何传递给 with-gensyms 的变量列表中所命名的变量。

    If you macro-expand the with-gensyms form in the new definition of do-primes, you should see something like this:

    如果在新的 do-primes 定义中对 with-gensyms 形式进行宏展开,就将看到下面这样的结果:

    Looks good. While this macro is fairly trivial, it’s important to keep clear about when the different macros are expanded: when you compile the DEFMACRO of do-primes, the with-gensyms form is expanded into the code just shown and compiled. Thus, the compiled version of do-primes is just the same as if you had written the outer LET by hand. When you compile a function that uses , the code generated by with-gensyms runs generating the do-primes expansion, but with-gensyms itself isn’t needed to compile a do-primes form since it has already been expanded, back when do-primes was compiled.

    看起来不错。尽管这个宏相对简单,但重要的是要清楚地了解不同的宏是分别在何时被展开的:当你编译关于 do-primesDEFMACRO 时,with-gensyms 形式就被展开成刚刚看到的代码并被编译了。这样,do-primes 的编译版本就已经跟你手写外层的 LET 时一样了。当编译一个使用了 do-primes 的函数时,由 with-gensyms 生成的代码将会运行用来生成 do-primes 的展开式,但 with-gensyms 宏本身在编译一个 do-primes 形式时并不会被用到,因为在 do-primes 被编译时,它早已经被展开了。