[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
compiler-let
COMPILER-LET seems pretty confused; CLtL says that it is intended
"for communication among complicated macros," but as far as I can tell
it doesn't reliably serve this end, since its effect is not the same in
interpreted and compiled code. Consider the following example:
(defvar *foo* nil)
(defmacro foo () `',*foo*)
(defun test ()
(compiler-let ((*foo* t))
#'(lambda () (foo))))
Now if TEST is "interpreted," then (funcall (test)) presumably returns
NIL; if it's "compiled," then (funcall (test)) presumably returns T.
Thus the binding of the variable *FOO* is not reliably communicated to
the macro FOO.
Of course, the fact that COMPILER-LET has different meanings in
"interpreted" and "compiled" code is in conflict with th "consistency"
statement on page 2. If COMPILER-LET is to be made meaningful, a
precise distinction between the interpreted language and the compiled
language must be made somewhere, and this is something I haven't seen so
far. Such a distinction would necessarily and absurdly cramp the styles
of "interpreters" and "compilers," e.g. by dictating when macro
expansion must happen (must a compiler expand macros? must an
interpreter avoid doing so until the code is actually executed?).
Is there any kind of thing which can be done using COMPILER-LET which
can't be done using MACROLET? E.g., if you want a macro FOO to expand
to one thing when a (FOO ...) form occurs lexically inside of a (BAR
...) form, but otherwise to something else, one is tempted to write
(defvar *inside-bar-p* nil)
(defmacro bar (&body body)
`(compiler-let ((*inside-bar-p* t))
(progn ,@body)))
(defmacro foo () `',*inside-bar-p*)
(foo) => nil
(bar (foo)) => t
but this fails in exactly the same way the previous example does:
(funcall (bar #'(lambda () (foo)))) => ??
(let ((fun #'(lambda () (foo)))) (bar (funcall fun))) => ??
MACROLET works much better; there are several ways to achieve the
desired effect.
(defmacro foo () ''nil)
(defmacro bar (&body body)
`(macrolet ((foo () ''t))
(progn ,@body)))
(foo) => nil
(bar (foo)) => t
(funcall (bar #'(lambda () (foo)))) => t
(let ((fun #'(lambda () (foo)))) (bar (funcall fun))) => nil
or, alternatively:
(defmacro foo (&environment e)
(if (macroexpand '(inside-bar-p) e) ''t ''nil))
(defmacro inside-bar-p () 'nil) ;macroexpands to t or nil
(defmacro bar (&body body)
`(macrolet ((inside-bar-p () 't))
(progn ,@body)))
etc. That is, the macro environment can be used reliably as a
communications device, without being sensitive to how the code is being
processed.
I'm surprised that people writing code-walkers haven't complained about
COMPILER-LET before (sorry if you have and I've forgotten). I can't
imagine what one is supposed to take COMPILER-LET to mean if one is
trying to understand the meaning of a program, instead of just trying
to run it.
If I had my way, I think I'd have COMPILER-LET excised, and if macros
need to communicate with each other (a dubious proposition in the first
place: can someone provide a realistic example?), the environments
passed by MACROEXPAND to macro expanders can be used as a communcations
channel.
Another alternative is to require that macros always be pre-expanded
even in "interpreted" code, and process COMPILER-LET's at this time;
this would synchronize the MACROEXPAND environment with special variable
bindings, so that source-code-lexical and expand-time-dynamic would mean
the same thing. But this would require nontrivial changes to
implementations.
Jonathan