On Thu, Jul 29, 2021 at 12:53 PM Michael Ballantyne <
michael.ballant...@gmail.com> wrote:

> The second example you give becomes more natural if you've considered
> simpler cases of macros in definition contexts first
>

I agree that breaking the macro into two parts—one of which inserts a
binder and one of which inserts a reference—is the most compelling way to
intuitively explain the behavior of the second expression. (And Shu-Hung
previously posted the same observation
<https://twitter.com/shhyou_886/status/1420281697673912321> on Twitter.)
Still, it is interesting how many people find the result deeply
unintuitive.

To understand why, it seems helpful to start from Clinger and Rees’ *strong
hygiene condition*, which provides a high-level definition of what hygiene
is supposed to mean:


>    1.
>
>    It is impossible to write a high-level macro that inserts a binding
>    that can capture references other than those inserted by the macro.
>    2.
>
>    It is impossible to write a high-level macro that inserts a reference
>    that can be captured by bindings other than those inserted by the macro.
>
> The precise meaning of these criteria is not completely obvious, because
the intended interpretation of the phrase “inserted by the macro” is not
perfectly clear. However, the intended interpretation seems to be that a
“macro-inserted” reference or binding is one for which the reference or
binding identifier itself does not come from the macro’s inputs.

This naturally justifies the interpretation of the first expression, since
by the above definition, the reference to x is macro-inserted, but the
binder for x is not. This lines up with the results of the survey:
programmers generally agree that the result of the first example ought to
be 'outer.

The second example is trickier, as at first blush it clearly violates the
second criterion of the hygiene condition: the macro-inserted reference to x
is captured by the non-macro-inserted binder. However, I don’t think the
applying the condition is quite so straightforward when recursive
definition contexts are involved.

My reasoning starts with contemplating the meaning of a macro like

    (define-syntax-rule (define-false x)
      (define x #f))

in isolation. What makes macros like this unusual is that they introduce
what one might call *free binders* by analogy to the notion of a free
reference. The first criterion of the strong hygiene condition clearly
always applies to macro-inserted *bound binders*, but it doesn’t always
seem apply to free ones. Why?

Intuitively, this is because the scope of a bound binder fundamentally
never contains the macro definition, which is the lexical location of any
macro-inserted reference. This invariant is broken by definition contexts,
where a free binder can eventually become bound in a scope that *does*
contain the macro definition. It is precisely this ability for a macro’s
use site to “retroactively” affect its definition’s scope that allows the
macro-inserted reference to be captured.

In other words, I don’t think this is actually a violation of the hygiene
condition because the macro-inserted reference is not actually “captured.”
Rather, the nature of recursive definition contexts necessarily allows
future definitions to affect earlier ones, and in this case, the future
definition of x is in scope at the macro’s *definition site*, which means
it must be in scope in the context of the macro-inserted x.

I think what’s so intuitively surprising about this essentially stems from
two things:

   1.

   After expansion, the macro-inserted reference to x appears *after* the
   internal definition, which means the binding structure of the expanded
   expression does not need to be recursive. A completely sequential structure
   would suffice, a la let*.
   2.

   If definition contexts were in fact sequential, not recursive, the
   result of the second expression would in fact be 'outer. This is because
   the internal definition of x would not be in scope at the macro’s
   definition, so the macro-inserted x should not be able to see it.

It is this bait-and-switch that seems to trip people up. The fact that the
macro-inserted reference appears after the internal definition in the
expansion makes it seem as though the macro-inserted reference is being
captured, but in fact it is not the structure of the expansion that
matters, but rather the structure of the original program. Indeed, there is
nothing special, magical, or broken about define itself, which introducing
a scope between macro definition and macro use site cleanly illustrates:

    > (let ([x 'outer])
        (define-syntax-rule (m a)
          (begin
            (define a 'inner)
            x))
        (let ()
          (m x)))
    'outer

Anyway, this email has ended up rather long, so perhaps it would be better
moved to a small blog post. But an explicit statement of the above
reasoning is precisely the sort of thing I have been looking for but have
not been able to find, so perhaps it will be useful to future readers.

Alexis

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/racket-users/CAA8dsafKWcBM7BiLH3pyDto0F%3DwzgPNns02f-n45VjmT5Yt-HA%40mail.gmail.com.

Reply via email to