Am Fr., 4. Okt. 2019 um 16:14 Uhr schrieb Lassi Kortela <xxxxxx@lassi.io>:
> Even if hygiene things and lexical scoping are neglected in the output, one
> cannot neglect them for the input of such a hypothetical "macroexpand" or
> "macroexpand-1". So either "macroexpand" has to be a special form (that is
> able to capture the lexical context, in which to do the expansion) or the
> argument to "macroexpand" already has to be a syntax object that is
> enriched with lexical information.
Can it cause unforeseen problems if macroexpand is a special form? I
can't think of any obvious problem with that, apart from the judgment
call that it's nice for everything to be a procedure whenever possible.

If macroexpand is a special form as I described, it is less flexible then the procedural version. The special form would always expand the program text in the lexical environment of that special form. The procedural version, however, would be given a syntax object (a syntactic closure), which does not need to be closed over the lexical environment, in which the call to macroexpand appears.

Thus, it is preferable if "macroexpand" becomes a procedure that takes a syntax object as its parameter. For this, the Scheme system should support the special form "(syntax <datum>)", which turns a datum into a syntax object closed over the lexical environment, in which the syntax-form appears. Any Scheme system supporting the R6RS and every Scheme supporting the syntax-case macro system (e.g. Chibi) already define  "syntax". If "syntax" is not provided, a quick-and-dirty solution would be to replace it with "quote", which, however, doesn't see the lexical environment.
 
R7RS `eval` takes an environment-specifier argument. But is the lexical
environment in which procedures are evaluated different from the
syntactic environment in which macros are expanded?

The environment argument of "eval" holds a top-level environment. This is not enough for macros defined locally.

The lexical environment, in which procedures are *evaluated*, is a run-time construct and thus not the same as the syntactic environment, in which these procedures (and macros) are expanded. Consider the following code:

(let ((x 42)) (lambda (f) ... x ...))

When expanding this expression, a syntactic environment is built. For example, in this environment, the syntactic entity "x" will be mapped to a location (e.g. a slot in the stack). When the inner lambda-expression is expanded, the meaning of "x" will be looked up in the syntactic environment.

Later, when the expanded expression is executed, the value associated with the location, to which "x" was mapped, has to be looked up. This is what happens in the runtime environment.
 

What's the usual environment when expanding macros from the REPL -- is
it different somehow from (interaction-environment)?

I think this should be the same.
 

Similar to `eval`, would a second argument defaulting to
(interaction-environment) work for macros or is it a lot more
complicated than that?

If you are only interested in expanding top-level macros, a version of "macroexpand" that takes an environment-specifier argument as a second argument and a bare Scheme datum as its first should be possible.

Marc

P.S.: We may have to be careful about what we want exactly. "Expansion" usually refers to translating a Scheme program into some form of an AST, which is then executed (or first compiled). As has been said before, the result of such a translation may not even be expressible in terms of Scheme data.

So, what we want, is less, namely just the (iterated) expansion of a macro call to user-defined macros so that the result is still a Scheme program (possibly annotated with hygiene information).

In some sense, the following should be equivalent:

(eval-syntax (macroexpand FORM ENV) ENV)   <---> (eval FORM ENV)

Here, I have borrowed the non-standard "eval-syntax" from Racket.