I've been building a library, Punctaffy, that has various notations similar to quasiquote
, quasisyntax
, etc. However, as I consider using these notations in macros' generated code, I notice something a lot like variable capture.
Here's a simple example without using the library at all, just using quasiquote
and unquote
:
#lang racket/base
(define-syntax-rule (test subform)
`(subform ,(+ 3 4)))
(writeln (test (+ 1 2))) ; prints "((+ 1 2) 7)"
(writeln (test ,(+ 1 2))) ; prints "(3 7)"
Essentially, an unquote
appearing in the argument of test
matches up with the quasiquote
in test
's expansion result.
There are two ways I like to think about this kind of syntax for Punctaffy's purposes:
- Occurrences of
quasiquote
andunquote
match up like brackets. The,(+ 1 2)
on the last line has an unbalancedunquote
bracket, which would ideally be detected as an error. - The
quasiquote
form is like a variable binding form, andunquote
is like a variable reference. The,(+ 1 2)
on the last line is therefore like a reference to a variable that has no local binding, and it would ideally be detected as an error. The fact that it's currently matching up with the binding introduced by thequasiquote
intest
's expansion is the kind of variable capture issue that Racket would usually avoid by giving thequasiquote
form's binding location an extra macro-introduction scope that theunquote
form's reference location doesn't have.
I think I ran across this earlier on in Punctaffy's development but decided to imitate the quasiquote
..unquote
scenario at first so as to ease the analogy between Punctaffy's new notations and Racket's existing ones. As long as all usage sites avoid these "error" situations in the first place, the fact that they actually silently proceed with some behavior isn't so bad.
However, as I try to prepare Punctaffy to be a useful library with reasonable compile times and a variety of new quasiquote
-shaped utilities, I find myself wanting to report this as an error more proactively rather than ignoring it. (In fact, I find myself wanting to track scopes on quasiquote
and unquote
for other reasons too, like maintaining additional variable bindings that are local to the region in between them.)
Is anyone attached strongly enough to the behavior of the example above that they'd consider it a mistake for me to diverge from it in Punctaffy? Any reason why?
I can imagine two reasons already:
- Backwards compatibility. At least for notations in the base Racket distribution like
quasiquote
andquasisyntax
, people might depend on this behavior already. If someone's defined a macro that expands into one of these, they've been able to count on this kind of variable-capture-like behavior without doing any explicit manipulation of scopes to achieve it. For this reason, I don't want to say Racket's notations should change, but it seems like Punctaffy has a chance to do something different. - Stylistic consistency. Notations like
quasiquote
andquasisyntax
are far from the only Racket macros that usefree-identifier=?
to detect occurrences of literals. They're just some of the more insidious since the places capture can occur are arbitrarily deep in a term. If I go down a path where my macros stop detecting literals the usualfree-identifier=?
way and start treating them as local bindings that participate in the sets-of-scopes system, are Racket programmers going to find them surprising and inconsistent with the rest of the language?
Left to my own direction, I'm probably going to have Punctaffy treat this as an error even if that breaks from convention, but I'd like to check in and see what others think.