Date: Mon, 17 Mar 2008 21:16:51 +0000 From: "David Rush" kumoyuki@gmail.com
SYNTAX-CASE and explicit renaming (Larceny & S48, IIRC) are the main ones I'm aware of. Are there other major contenders?
Explicit renaming is a special case of syntactic closures. I'm not sure whether Andre van Tonder's macro system (one of them high-numbered SRFIs) counts as another; in spirit it is explicit renaming with a dynamically scoped renaming procedure in the macro engine.
yet long experience has shown that this is almost always a mistake;
I never said anything different. But for the rare cases where hygiene breaking is desired, I find the semi-hygienic systems to be awfully cryptic.
In local macros that you plan to use in only one place to abbreviate the construction of elaborate names, it is not necessary to concern yourself with hygiene, and you can write a macro transformer procedure with explicit renaming or syntactic closures that looks identical to the DEFINE-MACRO version. But for anything beyond purely local macros, if you don't concern yourself with hygiene, however cryptic it may be, you are bound to lose later on.
Here is an exercise for the reader to illustrate the problem. Consider, for example, a DEFINE-ENUMERATION macro, defined so that
(define-enumeration foo (bar baz quux))
defines FOO to be a dispatching macro such that (FOO BAR) expands to 0, (FOO BAZ) to 1, and (FOO QUUX) to 2; and defines FOO-ENUMERANDS to be the vector #(BAR BAZ QUUX). Now suppose we write a SYNTAX-RULES macro that uses DEFINE-ENUMERATION:
(define-syntax mumble-frotz (syntax-rules () ((MUMBLE-FROTZ a b (c ...)) (BEGIN ... (DEFINE-ENUMERATION AN-ENUMERATION (c ...)) ... (FROB GROVEL AN-ENUMERATION-ENUMERANDS B))))).
Let's suppose DEFINE-ENUMERATION inserts the literal symbol AN-ENUMERATION-ENUMERANDS into the output:
(define-syntax define-enumeration (er-macro-transformer (lambda (form rename compare) ... (,(rename 'DEFINE) ,(string->symbol (string-concatenate (map symbol->string (list (name->symbol enumeration-name) '- 'ENUMERANDS)))) (,(rename 'QUOTE) ,(list->vector components))) ...))).
(We could even strip the renaming if you want, and make it look more like the analogous DEFINE-MACRO.) Why won't MUMBLE-FROTZ work? Why won't AN-ENUMERATION-ENUMERANDS in the output of MUMBLE-FROTZ refer to the variable we want?
And since I'm not interested in a religious advocacy war over hygiene, I'd like to know how you think module/namespace-based renaming is an adequate substitute for true hygiene in macros? Because I just don't see it.
I'm not interested in religious advocacy either; what I say here is a purely technical judgement.
Module-based renaming and macrotic renaming operate at sufficiently different levels of detail as to seem like rather different operators to me.
Hygienic renaming does not necessarily just mean affixing to each name a tilde or octothorpe and a big or random number that we hope is unique. Hygienic renaming means associating with each name its meaning, which may for local variables imply the affixing of funny-looking suffixes, but for names whose meaning must persist uniquely throughout a large system of components, it more likely implies associating some description of the module from which the name originated, or a path by which to get there. There is no fundamental, conceptual distinction between `module-based renaming' and `macrotic renaming'.
Take, for example, a DELAY macro, in some module that exports FORCE and DELAY and nothing else (perhaps throw EAGER and LAZY in to appease those of us who actually want to use laziness). We want to define the DELAY macro in terms of an internal MAKE-PROMISE:
(define-syntax delay (syntax-rules () ((DELAY expression) (MAKE-PROMISE (LAMBDA () expression))))).
But the relevant module does not export MAKE-PROMISE, so we can't just insert the literal symbol MAKE-PROMISE into the output, even if the user hasn't locally shadowed that name: if we insert the literal symbol in DELAY's output, it would be just as if the user had written MAKE-PROMISE in her own code, and she would encounter an undefined variable error.
Hygiene guarantees that the *name* (not `symbol') in the output of DELAY refers to exactly the same meaning as the symbol MAKE-PROMISE meant in the environment where the DELAY macro was defined. Exactly how this name is represented is inessential to the point of hygiene. It could be a qualified reference directly to the LAZINESS module, if we have a global naming scheme for modules. In Scheme48, it will be a `generated' name, which remembers: the name used to invoke the macro transformer that generated it, and the name by which the transformer identified it. Both names may be generated names themselves, but if we follow them all the way to the end, by recursively looking up the macro transformer, and looking up the name in that transformer's environment, we shall eventually end up with the original meaning we sought.
Whether this is the duty of the module system or of the macro system doesn't really matter -- that's an implementation detail, and requires only that the module system and the macro system cooperate. That it work is what matters.