Книга: Practical Common Lisp
Macro-Writing Macros
Macro-Writing Macros
Of course, there's no reason you should be able to take advantage of macros only when writing functions. The job of macros is to abstract away common syntactic patterns, and certain patterns come up again and again in writing macros that can also benefit from being abstracted away.
In fact, you've already seen one such pattern—many macros will, like the last version of do-primes
, 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?
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.
You want to be able to write something like this:
(defmacro do-primes ((var start end) &body body)
(with-gensyms (ending-value-name)
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body)))
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.
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
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.
CL-USER> (loop for n in '(a b c) collect `(,n (gensym)))
((A (GENSYM)) (B (GENSYM)) (C (GENSYM)))
After the list of binding forms, the body argument to with-gensyms
is spliced in as the body of the LET
. Thus, in the code you wrap in a with-gensyms
you can refer to any of the variables named in the list of variables passed to with-gensyms
.
If you macro-expand the with-gensyms
form in the new definition of do-primes
, you should see something like this:
(let ((ending-value-name (gensym)))
`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
(,ending-value-name ,end))
((> ,var ,ending-value-name))
,@body))
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 do-primes
, 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.
Another classic macro-writing MACRO: ONCE-ONLY |
Another classic macro-writing macro is
However, the implementation of
|
- 8. Macros: Defining Your Own
- A Macro Revolution
- DEFMACRO
- Beyond Simple Macros
- Writing Binary Objects
- 6.5 The Macro Ring
- 6.6 Binding Your Macro to a Key
- 6.7 Naming, Saving, and Executing Your Macros
- Chapter 6. Writing Macros
- CHAPTER 27 Writing PHP Scripts
- CHAPTER 33 Writing and Executing a Shell Script
- 5.3.1. The __setup Macro