[racket] Strange macro behavior with set! and syntax-local-introduce

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Fri May 16 02:47:11 EDT 2014

You're running into an awkward corner of the macro system. The corner
has to do identifiers that are produced by `generate-temporary` and
then defined within a module.

An identifier produced by `generate-temporary` doesn't claim to be from
any particular context. If such identifier is defined within a module
M, how do other uses of the identifier know to refer to the definition
of M (since the identifier by itself doesn't claim to be from M)?

The answer is that the definition marks the identifier as "introduced
by a macro into M". It also marks each top-level form in a module with
the "introduced to module M" context after expansion. The "top-level"
modifier on "form" is crucial here.

When you wrap `(funny-set! 3)` with `(void ...)`, then you delay its
expansion until after all top-level forms are expanded. That is,
`funny-set!` is not long a top-level form within the module.

 * If there's no `void`, then the `funny-set!` form is expanded, and
   the introduced variable is marked with the "introduced to M"
   context, and that matches up with the definition.

 * If there's a `void`, then the `(void (funny-set! 3))` form as a
   whole is marked with "introduced into M", but the `funny-set!` macro
   is not expanded, yet. Later, when `funny-set!` is expanded as a
   nested expression, then the introduced identifier is *not* marked
   with "introduced to M", and so it doesn't match up with the
   definition.

I'm itching to try a new macro expander that I think would solve this
problem, and maybe that will happen sometime this year.

Meanwhile, I would write `make-funny-set` as

 (define-syntax (make-funny-set! stx)
   (syntax-parse stx
     [(_ v)
      (define unmarked (generate-temporary))
      #`(begin
          (begin-for-syntax (set! funny (quote-syntax #,unmarked)))
          (define #,unmarked v))]))

and also

 (define-syntax (funny-set! stx)
   (syntax-parse stx
     [(_ v)
      #`(set! #,funny v)]))

By expanding into `begin-for-syntax`, you get the macro expander to
help you a little more: you don't have to use `syntax-local-introduce`.
Also, you also avoid the awkward corner, because the still-quoted
identifier is visible to the macro expander (and therefore available
for the "introduced to M" mark) instead of sitting in a side channel.

At Thu, 15 May 2014 16:25:47 -0500, Spencer Florence wrote:
> I'm attempting to write a macro which introduces a new id, then another
> macro that set!s that id.
> Example:
> 
> #lang racket
> (require (for-syntax syntax/parse racket/syntax))
> (define-for-syntax funny #f)
> (define-syntax (make-funny-set! stx)
>   (syntax-parse stx
>     [(_ v)
>      (define unmarked (generate-temporary))
>      (set! funny (syntax-local-introduce unmarked))
>      #`(define #,unmarked v)]))
> (define-syntax (funny-ref stx)
>   (syntax-parse stx
>     [(_)
>      funny]))
> (define-syntax (funny-set! stx)
>   (syntax-parse stx
>     [(_ v)
>      #`(set! #,(syntax-local-introduce funny) v)]))
> 
> (make-funny-set! 2)
> (funny-set! 3)
> (funny-ref)
> 
> This program works as I expect, evaluating to 3. However if I change
> (funny-set! 3) to (void (funny-set! 3)) I get the error: "set!: unbound
> identifier in module in: g1"
> 
> I do not get this error if I change (funny-ref) to (void (funny-ref)).
> 
> If I look at the expansion of the (void (funny-set! 3)) program in
> drracket's macro stepper the the g1 in (define g1 2) and the g1 in (void
> (set! g1 3)) have the same color.
> 
> To keep on with the strange, if I change the #,(syntax-local-introduce
> funny) inside of funny-set! to #,funny inside the behavior of all programs
> remains the same.
> 
> Could someone explain whats going on?
> 
> --Spencer
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users

Posted on the users mailing list.