[racket] macro calling itself

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Mon Oct 27 16:24:12 EDT 2014

We sometimes call this the "non-hygienic macros compose poorly"
problem.

The two-subform case of `definer` uses the overall form, `stx`, to
provide a content for the introduced `arg-id` binding. So, when you use
the two-subform variant directly in some context, then you can see the
`arg-id` binding in that same context.

When you use the one-subform `definer`, it expands to a two-subform
`definer`, but that new `definer` form is macro-introduced.
Consequently, further expansion also marks `arg-id` as
macro-introduced, and so `arg-id` becomes inaccessible from the context
that used the one-subform `definer`.


In this case, using the given `id` as the lexical context for the
synthesized name (instead of the overall `definer` form) is almost
certainly what you want:

  (format-id #'id "~a-~a" #'arg #'id)

That makes `zam-baz-id` visible in the same contexts as `baz-id`. It's
still a non-hygienic macro in some sense, but it's an ok kind, because
the context for the introduced binding comes from a sensible part of
the macro use.


At Mon, 27 Oct 2014 13:10:39 -0700, Matthew Butterick wrote:
> I'm unclear why I'm getting an unbound-identifier error in this macro. I've 
> written other self-referencing macros in the past that have worked, so I'm 
> unclear why this one doesn't. 
> 
> What I expect is that when `definer` is called with one argument, the first 
> branch of `syntax-case` will stick on a default argument and call `definer` 
> again. Which it does, except that in that case, it only binds the name 'id' 
> rather than both 'id' and 'arg-id'. 
> 
> 
> #lang racket
> (require (for-syntax racket/syntax) rackunit)
> 
> (define-syntax (definer stx)
>   (syntax-case stx ()
>     [(_ id)
>      #'(definer id zam)]
>     [(_ id arg)
>      (with-syntax ([arg-id (format-id stx "~a-~a" #'arg #'id)])
>        #'(begin
>            (define id (quote id))
>            (define arg-id (quote arg-id))))]))
> 
> 
> (definer foo-id bar)
> (check-equal? foo-id 'foo-id)
> (check-equal? bar-foo-id 'bar-foo-id)
> 
> (definer baz-id)
> (check-equal? baz-id 'baz-id)
> (check-equal? zam-baz-id 'zam-baz-id) ;; unbound identifier error
> 
> 
> OTOH, this version of the macro, which repeats the expansion template rather 
> than calling itself, works fine:
> 
> 
> #lang racket
> (require (for-syntax racket/syntax) rackunit)
> 
> (define-syntax (definer2 stx)
>   (syntax-case stx ()
>     [(_ id)
>      (with-syntax ([arg-id (format-id stx "~a-~a" (format-id stx "zam") #'id)])
>        #'(begin
>            (define id (quote id))
>            (define arg-id (quote arg-id))))]
>     [(_ id arg)
>      (with-syntax ([arg-id (format-id stx "~a-~a" #'arg #'id)])
>        #'(begin
>            (define id (quote id))
>            (define arg-id (quote arg-id))))]))
> 
> 
> (definer2 foo-id bar)
> (check-equal? foo-id 'foo-id)
> (check-equal? bar-foo-id 'bar-foo-id)
> 
> (definer2 baz-id)
> (check-equal? baz-id 'baz-id)
> (check-equal? zam-baz-id 'zam-baz-id) ;; no error this time
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users

Posted on the users mailing list.