Hi Daphne,

the R6RS gives in [1, section 12.1] the following hygiene condition:

"A binding for an identifier introduced into the output of a transformer call from the expander must capture only references to the identifier introduced into the output of the same transformer call.  A reference to an identifier introduced into the output of a transformer refers to the closest enclosing binding for the introduced identifier, or, if it appears outside of any enclosing binding for the introduced identifier, the closest enclosing lexical binding where the identifier appears (within a "syntax" template) inside the transformer body or one of the helpers it calls."

Now, let's look at the expansion of the first example:

(let ([x 'outer])
  (define-syntax m
    (lambda (stx)
      (syntax-case stx ()
        [(_ a)
         (syntax (let ([a 'inner]) x)])))
  (let ([x 'inner])
    x*))

Here, I have marked the identifiers named "x" that are introduced into the output of the transformer call with an asterisk.  The second clause of the second sentence in the above quote dictates what "x*" refers to, namely to the closest enclosing lexical binding where it appears inside the transformer body.  In this case, it is the binding of "x" to (a location containing) the symbol "outer".

The expansion of the second example is (after splicing the "begin" form):

(let ([x 'outer])
  (define-syntax m
    (lambda (stx)
      (syntax-case stx ()
        [(_ a)
         (syntax (begin
                   (define a 'inner)
                   x))])))
  (define x 'inner)
  x*)

Again, no binding of "x*" is introduced into the output of the transformer call, so again "x*" refers to the closes enclosing lexical binding where it appears inside the transformer body. The closest lexical binding to where it appears is the binding of "x" to the symbol "inner" because by [2, section 11.3], the region of the binding by a local definition is the entire body, which includes the transformer body.

Thus a result of outer/inner is the correct one according to the R6RS.

The question does not apply to the R5RS because by [3, section 5.3] there is no "define-syntax" analog of internal definitions.

Let us finally take a look a the R7RS.  In [4, section 5.3.2] it says again that the region of a local variable definition is the entire body. What is missing from the R7RS when compared to the R6RS is the quoted paragraph from the beginning. Instead, it uses the following language in [4, section 4.3], which also appears in [2, section 9.2]:

"If a macro transformer inserts a free reference to an identifier, the reference refers to the binding that was visible where the transformer was specified, regardless of any local bindings that may surround the use of the macro."

In both examples, "x*" is inserted as a free reference, so, again, it refers to the closest lexical binding of the identifier to the transformer.  In the first example, it is the binding of "x" to the symbol "outer", in the second example, it is the binding of "x" to the symbol "inner", which is visible in the entire body.

Thus, also to the R7RS, a result of outer/inner is the correct one.

We can therefore conclude that the two examples are less about the hygiene condition for macro expansion but about the scope of internal definitions, which is fully defined by the R6RS and the R7RS.  As far as CHICKEN claims to be compatible with R7RS, it gets it wrong.  As an R5RS implementation, no error has been exposed because the two examples are not valid R5RS expressions.

Best,

Marc

PS I haven't yet read the Twitter stream you linked.

--

[1] R6RS Standard Libraries
[2] R6RS
[3] R5RS
[4] R7RS

Am Mi., 28. Juli 2021 um 23:11 Uhr schrieb Marc Nieper-Wißkirchen <xxxxxx@gmail.com>:
Thanks for pointing out this nice example. It's too late for me to do it now, but I will post an analysis tomorrow. The main point is that `let' creates a new lexical contour. In the second example, the "x" is bound to "inner" in the transformer environment.

Am Mi., 28. Juli 2021 um 21:22 Uhr schrieb Daphne Preston-Kendal <xxxxxx@nonceword.org>:
Alexis King posed the following brain teaser on Scheme and asserted that it’s a case where the semantics of pattern-matching and identifier renaming are poorly-defined.

> If you know Scheme: without checking, what do you think the following expressions evaluate to?
>
> (let ([x 'outer])
>   (define-syntax-rule (m a)
>     (let ([a 'inner]) x))
>   (m x))
>
> (let ([x 'outer])
>   (define-syntax-rule (m a)
>     (begin
>       (define a 'inner)
>       x))
>   (m x))
<https://twitter.com/lexi_lambda/status/1420221128245714951>

I don’t want to start an informal debate about ‘intuitive’ reasons for one or the other answer here (Twitter is the place to do that, and the diversity of replies suggest that such a discussion could be never-ending), but rather ask if this really is a poorly-defined case in syntax-rules/syntax-case pattern replacement according to R5/R6/R7RS, and if so whether SRFI 211 might be able to tighten this up, at least in the case of syntax-case.

For the record, Racket, Chez, Guile, Gerbil, Chibi, Sagittarius, and MIT Scheme (altogether, at least five independent implementations of syntax-rules, I think) agree that the results are 'outer, 'inner. Chicken thinks it’s 'outer, 'outer, as do an absolute majority of respondents to Alexis’s poll as at time of writing. I don’t have other implementations at hand to test on.


Daphne