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.