2 ms·
Your intuition that Clojure's macro system is unhygienic are correct. Hygiene in this context was coined by Henk Barendregt in the context of the lambda calcul
by groovy2shoes 10y ago
Your intuition that Clojure's macro system is unhygienic are correct.
Hygiene in this context was coined by Henk Barendregt in the context of the lambda calculus:
> For reasons of hygiene, it will always be assumed that the bound variables that occur in a certain expression are different from the free ones. [0]
This may seem nonsensical at first, but consider two different functions, f(x) and g(x): although both f and g take an x, they are in fact different variables. So, if some expression has x free, and another has x bound, and one is substituted into the other, you must remember to differentiate between the two homonyms. One way to do this is to consistently rename one or both variables so that they no longer appear to be the same, but it's important not to get too hung up on the concept of renaming per se, because the only requirement is that they be regarded as different (Andre van Tonder's delightful implementation of syntax-case uses a painting algorithm rather than a renaming one, but the net effect is the same).
In the case of syntactic transformers (macros), we're not concerned about the variables of the transformer itself, but rather the variables of the transformed code. Consider that full macro expansion occurs as a sequence of single expansions (transcription steps) until no further expansion can be done. Now, a macro is hygienic if all identifiers introduced during any given transcription step are unique to that transcription step (regardless of name). The introduced identifiers are analogized with bound variables while all other identifiers are analogized with free variables, just like how a lambda abstraction introduces its parameter variable.
Note that even unhygienic macro systems are capable of expressing at least some hygienic macros: macros that do not introduce any identifiers are trivially hygienic. I contend that the explicit-renaming macro system is not actually hygienic, even though it allows you to write any hygienic macro. The reason being that, in essence, hygienic macro expansion is precisely the notion of lexical scoping applied to syntactic transformation; and just as we don't need to manually specify the scope of each variable in our basis language, we should not need to do so in our meta language. For the same reason, Clojure's requirement to gensym identifiers -- while it offers convenient shorthand for the operation, it is still explicit (and it solves only one of the two problems of unhygienic expansion, namely variable capture, while providing no protection from shadowing). In that vein, syntax-rules, syntax-case, and implicit-renaming macros all fit the bill.
Just as dynamically-scoped variables can come in handy sometimes even in a lexically-scoped language (many Lisps have them, variously called "fluid variables", "special variables", or "parameters"), unhygienic injection of identifiers can come in handy sometimes (the classic example being anaphoric macros). The traditional way to do this is to simply circumvent, or "break", hygiene for a particular identifier, but Eli Barzilay and crew have also come up with the notion of "syntactic parameters" analogous to dynamically-scoped variables.
[0]: Henk Barendregt, "Introduction to the Lambda Calculus".
- qwertyuiop924 10y agoNeat. But er IS actually hygenic by the standard definition. I'd say I'd check out what Eli &co did, but I probably won't. TBH, Racket leaves a bad taste in my mouth: the core is too complex for such little benefit. If some of it were in libraries, I wouldn't mind so much, but most of it's there by default. And I share Alex Shinn's opinions on syntax-case.
- groovy2shoes 10y ago> Neat. But er IS actually hygenic by the standard definition. It's hygienic in that it allows you to write any hygienic macro, yes. However, it's not "hygienic by default" -- you have to go out of your way to manually and explicitly ensure your er-macros are hygienic. Leave out a ,(rename 'foo) somewhere, and suddenly you've broken hygiene. Same problem with gensym, really, except that rename is a little nicer to use. Compare with syntax-rules, syntax-case, or ir-macros, where any macro you write is hygienic automatically and by default -- you have to go out of your way to manually and explicitly inject free identifiers. > I'd say I'd check out what Eli &co did, but I probably won't. TBH, Racket leaves a bad taste in my mouth: the core is too complex for such little benefit. It's also a proposed addition for R7RS-large (SRFI-139). > If some of it were in libraries, I wouldn't mind so much, but most of it's there by default. It's all in libraries. `#lang racket` is just a "metalibrary" that exports a bunch of smaller libraries. You can always do `#lang racket/base` and include the subset of libraries you need. > And I share Alex Shinn's opinions on syntax-case. Not to talk shit about Alex -- he's a smart guy and Chibi is a very nice system -- but he's both seriously misinformed about syntax-case and hopelessly obstinate about it. Despite his claims that syntax-case is super complicated to implement, it's really not much more complicated than syntax-rules: it uses the same pattern language, but rather than a template, it's associated with an expression. The only thing "extra" that's needed is a disjoint `syntax` type, which is just a wrapper around the other primitives with a bit of implementation-defined context (the crucial bit of context, for purposes of hygiene, being the syntactic environment; apart from API, a `syntax` value is really just a syntactic closure). The thing is, syntax-case has been shown to be able to express anything er-macros and synclos can express, but the reverse is not true, meaning that it's very possible that syntax-case is strictly more powerful than the alternatives. I honestly think Dybvig and company did a disservice to syntax-case by making psyntax so complicated. I think one of their goals was to make psyntax production-quality, but I think it would have been better to make it as simple as possible -- just the bare essentials, for illustrative purposes. People tend to take it as exemplary because it's what they've got, but it really is a bad example because in its desire to be real-world and practical it obscures the intuition behind the system. Dybvig's entry in "Beautiful Code"[0] helps considerably, but I still maintain that the essence of the system is simpler than it's portrayed even there. While I think that ir-macros are often very nice, there's no denying that the pattern matching offered by syntax-rules/syntax-case is extremely convenient and, more often than not, extremely clear. Many of the macros that are full of cadrs and caaaddddrs and cddaaars find a much more convenient and readable expression in the pattern language. [0]: http://www.cs.indiana.edu/~dyb/pubs/bc-syntax-case.pdf http://www.cs.indiana.edu/~dyb/pubs/bc-syntax-case.pdf