Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require that macro literals always be bound. #55

Open
gus-massa opened this issue Jul 21, 2019 · 8 comments
Open

Require that macro literals always be bound. #55

gus-massa opened this issue Jul 21, 2019 · 8 comments

Comments

@gus-massa
Copy link

  • 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.

@gus-massa
Copy link
Author

I don't like it, but on the other hand this would have saved me from typos like a million times.

@sorawee
Copy link
Contributor

sorawee commented Jul 22, 2019

I spent a lot of time last night finding a bug which is caused by a typo: or* should have been ~or* :(

@jackfirth
Copy link
Sponsor Collaborator

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.

@rmculpepper
Copy link

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.

@rocketnia
Copy link

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 #:literals option to syntax-parse and the literal-id parts 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 of syntax-rules since 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 for syntax-case or syntax-parse to declare an unbound literal in the first place.

  • The issue @sorawee is talking about is that syntax-parse patterns silently treat {or* a b} as a pattern that matches a list with three elements or*, a, and b, 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 syntax templates 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 Lisp defmacro/quasiquote programmers create several gensyms in a let outside 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."

@jeapostrophe
Copy link
Collaborator

@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.

@jackfirth
Copy link
Sponsor Collaborator

jackfirth commented Jul 23, 2019

Well said @rocketnia! And yes, just to clarify, I meant for-template and not for-label. Oops.

@sorawee
Copy link
Contributor

sorawee commented Sep 2, 2019

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 :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants