[racket-dev] Enhancement to the syntax system?

From: Eli Barzilay (eli at barzilay.org)
Date: Tue Jul 10 12:48:04 EDT 2012

An hour and a half ago, Matthew Flatt wrote:
> It's natural --- but not correct --- to think that #` is responsible
> for hygiene, in which case `(f #'x)' should keep the given `x'
> separate from the `let'-bound `x' in the result.
> Instead, hygiene is the responsibility of macro invocation, and
>  #`(let ([x 1]) #,#'x)
> is simply the same as
>  #`(let ([x 1]) x)
> and so
>  (f #'x)
> above is equivalent to
>  #`(let ([x 1]) x)
> If you change the example to
>  #lang racket
>  (begin-for-syntax 
>   (define-syntax-rule (f body)
>     #`(let ([x 1]) body)))
>  (define-syntax (m stx)
>    (with-syntax ([zz (f x)]) #`(let ([x 2]) zz)))
>  (m)
> so that `f' is used as a macro instead of a function, then you get 2,
> since the macro-expansion of `(f x)' keeps the `x's separate.

Yeah, I think that this kind of confusion is there, but it's easy (at
least "relatively easy") to build a mental model of how things work
and avoid such problems -- but the tricky bit here is that things
break when the `stx' is renamed to `x'.

50 minutes ago, Ryan Culpepper wrote:
> The difference between
> (define-for-syntax (f1 stx) #`(let ([x 1]) #,stx)
> and
> (define-for-syntax (f2 x) #`(let ([x 1]) #,x)
> is that the 'x' in the template in the 'f2' is not bound-identifier=? to 
> the 'x' that appears in the template of 'm', because it has a rename 
> wrap due to the 'x' formal parameter of 'f2'. That is, 'f2' behaves 
> essentially the same as
> (define-for-syntax (f2* x*) #`(let ([x* 1]) #,x*)

Yeah, I think that this is the more confusing bit.  (I suspected
something along this line, but didn't have time to figure out a good
explanation...)  So I think that the real confusion is actually a
combination of what Matthew pointed at in the above and this one,
making the result trickier than both.  In other words, I *think* that
the effect of the transformer's argument name makes it looks like the
#` *was* responsible for hygiene when it's actually just this point
that makes it happen...  (This is all "IIUC".)

> A likely mistake is to think that the wrap generated by the 'x' in
> 'f2' doesn't count because it happens before we get around to the
> "real" macro expansion that you care about. But that's not the case
> (at least in Racket).
> A good rule of thumb is to never use local variable names in your
> macro implementation (including compile-time auxiliary functions)
> that also appear in the macro's template (including etc).

Yeah, and a subtle lesson here is that `stx' is a useful convention.
(I think that `x' is common in some guile code, so this would be a
point for them.)

> A related error is the "identifier used out of context" error that
> you get from, eg,
> (define-syntax (m stx)
>    (let ([+ *])
>      #'(+ 1 2)))
> (m)  ;; => identifier used out of context in: +
> The binding of '+' in the macro changes the meaning of the '+' in
> the template, even though the bindings exist at different
> phases. You could perhaps avoid this issue by changing the hygiene
> algorithm by adding a phase level to rename wraps and skipping
> different-phase rename wraps during resolution. I'm not sure if this
> is a good idea or if anyone has tried it.

(And this is what Matthew's last example gets by changing `f' to a
macro, right?  Also, Stefan posted a related message to the
scheme-reports list where he imlpemented some new #. thing which is
(roughly speaking) something that expands to a `let-syntax' and
therefore tries to do the same.)

In any case, it would be nice if the original example I posted (which
is a variant of what was discussed originally) would throw an error
instead of looking right in a way that is wrong...

          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                    http://barzilay.org/                   Maze is Life!

Posted on the dev mailing list.