From: Kent M Pitman
Subject: Re: recursive macroexpansion
Date: 
Message-ID: <sfwhg14cdlm.fsf@world.std.com>
Sam Steingold <···@goems.com> writes:

> 
> I know of macroexpand and macroexpand-1, but they do macroexpansion only
> for the top-level macro.  E.g.:
> 
> (macroexpand '(macro1 (macro2) (macro3)))
>         => (something-to-which-macro1-finally-expands (macro2) (macro3))
> 
> Is there a simple way to recursively macroexpand a form?  Like:
> 
> (macroexpand-r '(macro1 (macro2) (macro3)))
>         => (something-to-which-macro1-finally-expands
>                 (something-to-which-macro2-finally-expands)
>                 (something-to-which-macro3-finally-expands))
> 
> The following
> 
> (defun macroexpand-r (form)
>   (if (atom form) form
>       (let ((res (macroexpand form)))
>         (cons (car res) (mapcar #'macroexpand-r (cdr res))))))

To see the problem inherent in this, consider:  

  (let ((let 3)) let)

You're likely to get a destructuring error while recursing into the
let bindings because you are treating arbitrary subexpressions as
if they were forms.

To see the issue more clearly, consider that in MDL (pronounced "muddle"),
there were two kinds of parens used.  <...> denoted forms and (...) denoted
arbitrary syntax introduced by forms.  Without worrying about the other 
details of the language, we can see that we might say:

 <LET ((LET 3)) LET>

and that it's meaningful to recurse down <...>'s but not down (...)'s
becuase the expression (LET 3) is not a form.  Only the whole expression
<LET ((LET 3)) LET>, the constant 3, and the last occurrence of LET are
forms.

Another example of where you'll lose is:

 (macrolet ((frob (&rest forms)) `(progn ,@(cdr forms)));discard first form
    (frob (this part is ignored) 3))

which ought to macroexpand to (progn 3) but that will
instead macroexpand with your expander to

 (macrolet ((frob (&rest forms)) `(progn ,@(cdr forms)))
    (frob (this part is ignored) 3))

because frob won't get macroexpanded.  You might not think that
was so bad, but if there was a gloal
 (defmacro frob (&rest forms) `(progn ,@forms)) ;DON'T discard first form
then 
 (macrolet ((frob (&rest forms)) `(progn ,@(cdr forms)))
    (frob (this part is ignored) 3))
would macroexpand to
 (macrolet ((frob (&rest forms)) `(progn ,@(cdr forms)))
    (progn (this part is ignored) 3))
which would NOT be good.

The solution to this is complex.  Almost every system has a facility
like you want, but the facilities are very different in style and
implementation.  Some use dynamic state, some don't.  We started to
address this and the partial start is reflected in functions that
appeared in CLTL2 and not in the final ANSI SPEC for querying and
constructing environment args during macro expansion and other code-walking.
We found so many bugs in what we had, though, that we backed out of it.
So all solutions to this right now are implementation-specific.
From: Kent M Pitman
Subject: Re: recursive macroexpansion
Date: 
Message-ID: <sfw7m1zagm9.fsf@world.std.com>
Sam Steingold <···@goems.com> writes:

>  >> The solution to this is complex.  Almost every system has a facility
>  >> like you want, but the facilities are very different in style and
>  >> implementation.  Some use dynamic state, some don't.  We started to
>  >> address this and the partial start is reflected in functions that
>  >> appeared in CLTL2 and not in the final ANSI SPEC for querying and
>  >> constructing environment args during macro expansion and other code-walking.
>  >> We found so many bugs in what we had, though, that we backed out of it.
>  >> So all solutions to this right now are implementation-specific.
> 
> I must admit that this is surprising.
> The compiler does it - there are no macros in the compiled functions,
> (at least ANSI seems to say so when defining compiled functions).
> So, disassembling the result of compilation should do what I want!
> (unfortunately, disassemble prints some assembler-like code, which is
> not very helpful).
> 
> Why can't `compile' take an option to "only expand the macros"? :-)

Because in the structure of compilers varies quite widely and the main
thing that makes this hard at all is not "this is conceptually
difficult" but "the PRE-existing design of compilation and macro
expansion environments did not anticipate the need for a standardized
solution".  Put plainly: It's not that it couldn't, it's more that it
didn't.  And that's often the real hard point about standards that
people don't understand--think of it as an exercise in backtracking in
a system that didn't anticipate the need backtracking and in which
every computational step (backing out of one implementation or moving
forward into another) costs a LOT Of money and that will put the issue
into perspective for you.

The problem is the same with the MOP.  Doing a MOP is not hard--in
many ways, having the shape of the substrate pre-specified simplifies
some problems.  But if you don't pre-specify, you find that people do
it different ways.  And so when you try to reveal the MOP and say
"everyone does it the same way" you find (not surprisingly) that
there's so much variance that getting agreement on a standard is hard.
So most systems have a MOP, but there is no sure-to-be-right central
MOP spec because details differed and were not required by any
standard.

Back to macroexpansion, in the case of the compiler, the information
in many compilers is not in an object.  In some cases it's in the dynamic
state of the compiler instead.  And so it doesn't lend itself to packaging
up into "environment objects".  You could cheat and say
 (defclass macro-env () ())
 (defmethod local-macros ((m macro-env)) *local-macros*)
 ...
making an object that pretends to be modular but really gets the info
from the dynamic environment, and that would work in MOST cases because
really most people don't want to do anything more than 
 (local-macros (current-env))
anyway, and that's just as good as a *local-macros* reference.  But if
you saved the object and passed it in to some different place, you'd
make a big mess.  And that's one of several problems implementors had when
asked to make their implementations conform.

Compilers also do additional macro expansion that user programs may
not.  They may treat special forms as expandable into sub-primitives
you know nothing about.  And they may be really bad at code-walking
those same primitives without speciall macroexpanding them because
normally they don't do it.  

Also, although you didn't ask for it, this issue is bundled up in the
issue of asking what declarations are in effect.  And in that case,
you can't just run the compiler and tell it "macros and decls only"
since the decls may be processed in a post-pass and not available
at macroexpansion time.  Or the entire expression may be converted
by the compiler to something that is not a "form".  Lots of things 
can happen because you're in an area that was wholly unconstrained 
and left to implementors to choose how to do.

Hope this helps.