[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