207
Well, technically those constructs could also expand into a LAMBDA
expression since, as I mentioned in Chapter 6, LET
could be defined—and was in some earlier Lisps—as a macro that expands into an invocation of an anonymous function.
208
Surprising as it may seem, it actually is possible to make anonymous functions recurse. However, you must use a rather esoteric mechanism known as the
209
It's not required that WITH-SLOTS
be implemented with SYMBOL-MACROLET
—in some implementations, WITH- SLOTS
may walk the code provided and generate an expansion with x
, y
, and z
already replaced with the appropriate SLOT- VALUE
forms. You can see how your implementation does it by evaluating this form:
(macroexpand-1 '(with-slots (x y z) obj (list x y z)))
However, walking the body is much easier for the Lisp implementation to do than for user code; to replace x
, y
, and z
only when they appear in value positions requires a code walker that understands the syntax of all special operators and that recursively expands all macro forms in order to determine whether their expansions include the symbols in value positions. The Lisp implementation obviously has such a code walker at its disposal, but it's one of the few parts of Lisp that's not exposed to users of the language.
210
One version of f2cl is available as part of the Common Lisp Open Code Collection (CLOCC): http://clocc.sourceforge.net/
. By contrast, consider the tricks the authors of f2j, a FORTRAN-to- Java translator, have to play. Although the Java Virtual Machine (JVM) has a goto instruction, it's not directly exposed in Java. So to compile FORTRAN gotos, they first compile the FORTRAN code into legal Java source with calls to a dummy class to represent the labels and gotos. Then they compile the source with a regular Java compiler and postprocess the byte codes to translate the dummy calls into JVM-level byte codes. Clever, but what a pain.
211
Since this algorithm depends on values returned by RANDOM
, you may want to test it with a consistent random seed, which you can get by binding *RANDOM- STATE*
to the value of (make-random-state nil)
around each call to algorithm-s
. For instance, you can do a basic sanity check of algorithm-s
by evaluating this:
(let ((*random-state* (make-random-state nil))) (algorithm-s 10 200))
If your refactorings are all valid, this expression should evaluate to the same list each time.
212
This is a pretty reasonable restriction—it's not entirely clear what it'd mean to return from a form that has already returned—unless, of course, you're a Scheme programmer. Scheme supports
213
If you're the kind of person who likes to know how things work all the way down to the bits, it may be instructive to think about how you might implement the condition system's macros using BLOCK
, TAGBODY
, closures, and dynamic variables.
214
UNWIND-PROTECT
is essentially equivalent to try/finally
constructs in Java and Python.
215
And indeed, CLSQL, the multi-Lisp, multidatabase SQL interface library, provides a similar macro called with-database
. CLSQL's home page is at http://clsql.b9.com
.
216
A small handful of macros don't pass through extra return values of the forms they evaluate. In particular, the PROG1
macro, which evaluates a number of forms like a PROGN
before returning the value of the first form, returns that form's primary value only. Likewise, PROG2
, which returns the value of the second of its subforms, returns only the primary value. The special operator MULTIPLE-VALUE- PROG1
is a variant of PROG1
that returns all the values returned by the first form. It's a minor wart that PROG1
doesn't already