rhombus-prototype
rhombus-prototype copied to clipboard
Require that macro literals always be bound.
- Require that macro literals always be bound.
Extracted from the old racket2 wiki. #33
Perhaps it should be merged with a more general discussion about syntax-case/syntax-parse.
I don't like it, but on the other hand this would have saved me from typos like a million times.
I spent a lot of time last night finding a bug which is caused by a typo: or* should have been ~or* :(
Hell, I'm tempted to go further and require that template identifiers always be bound with for-label imports. Forgetting to require things for-label has bitten me countless times and it never shows until the macro is used. It feels like writing javascript with misspelled object property names all over again.
I'm confused when for-label is relevant. Do you mean for-template? In any case, requiring every identifier in a macro template to be bound would interfere with quoted symbols and identifiers used as binders. For example, in
(define-syntax-rule (greeter) (lambda (x) (list 'hello x)))
both hello and x appear unbound with respect to the macro template.
If I understand everyone correctly, I'm seeing three topics here -- all of which I agree are issues -- which have little relation with each other. They're related only by the fact they're all about silent defaults for unbound identifiers and they all have to do with DSLs for macro writing.
-
The issue in the OP is about the
#:literalsoption tosyntax-parseand theliteral-idparts of(syntax-case stx-expr (literal-id ...) clause ...). I'm not entirely sure what happens right now if they're not bound, but it looks like a pattern with an unbound literal matches as long as the identifier is also unbound at the macro's usage site. (I notice this has been consistent in the Scheme spec ofsyntax-rulessince it was introduced in R4RS.[1]) This means the macro user may like to surround their macro call with an "unrequire" or "unlet" to make sure the identifier is unbound, which I don't think Racket makes easy to do. Since this frustrating experience is likely not what the macro author intended, the OP's talking about making it an error forsyntax-caseorsyntax-parseto declare an unbound literal in the first place. -
The issue @sorawee is talking about is that
syntax-parsepatterns silently treat{or* a b}as a pattern that matches a list with three elementsor*,a, andb, rather than as an erroneous usage of the~or*pattern operator. I think a fix to this would be to remove list patterns(a b c)in favor of something explicit like{~list a b c}. -
The issue @jackfirth is talking about is that
syntaxtemplates silently succeed when an identifier is not bound. I can see this being a problem for at least one reason: Because it means a misspelled reference to a module-level binding (or a correctly spelled reference to a forgotten import) is misunderstood as a call to a fresh (and unbound) local variable. I think a fix to this would mean that most local variables and quoted symbols in templated code would have to be forward-declared outside the template, not unlike the way Common Lispdefmacro/quasiquoteprogrammers create several gensyms in aletoutside their quasiquotation. When users are explicit about intent this way, it's possible to detect an error when a variable is apparently intended to be a nonlocal reference but has no actual nonlocal binding.
Personally, the latter two are things that I've thought of as annoyances with Racket's macro system for the past couple of years (ever since I started seriously learning it). I remember being particularly disappointed to learn syntax templates just silently accepted every variable name without question, rather than somehow magically knowing how to parse let and define to detect binding occurrences. However, applying discipline and being cognizant of the gotchas is usually enough to make these quirks tolerable, so I've simply thought of them as things a new system should avoid -- hence, things to bring up now for Racket 2.
Finally, I agree with the OP's issue with literals, but I want to add that bound literals are also a problem. (I expressed some related concerns and ideas on another issue the other day, so this might sound familiar.)
Bound literals (and free-identifier=? checks more broadly) mean macros can recognize an identifier that has a known binding... but then implement behavior for it in a way that's inconsistent with that binding, which really calls into question whether a free-identifier=? check yields any meaningful information in the first place. Sure it tells us two identifiers may be intended to refer to the same module binding, but every so often two free-identifier=? identifiers actually have different meanings, because one of them is intended for use with one of these special-casing macros.
If macros are going to give an identifier a new meaning, they should do so using an explicit, hygienic binding occurrence. And if they do not intend to bind a local meaning, then they should really be making use of the nonlocal meaning (e.g. using syntax-local-value) rather than implementing the behavior themselves.
[1] R4RS description of syntax-rules: "A subform in the input matches a literal identifier if and only if it is an identifier and either both its occurrence in the macro expression and its occurrence in the macro definition have the same lexical binding, or the two identifiers are equal and both have no lexical binding."
@rocketnia This is an excellent summary of the issues of concern. This is exactly the reason why these discussions are so valuable before writing up a detailed RFC proposing some solution. Thank you.
Well said @rocketnia! And yes, just to clarify, I meant for-template and not for-label. Oops.
I spent a lot of time today finding a bug which is caused by a typo: #:literals (kernel-literals) should have been #:literal-sets (kernel-literals). Yet another :(