macrosrackethygiene

Racket macro that generates a nested module error


While experimenting with racket's macros, I stumbled into a definition that wasn't at first obvious to me why it was rejected. The code is short and otherwise is probably useless, but is as follows:

#lang racket
(define-syntax (go stx)
  (syntax-case stx ()
    [(_ id)
     #'(module mod racket
         (define it id))]
    ))
(go 'dummy)

The complaint is quote: unbound identifier; also, no #%app syntax transformer... If I manually inline (define it id) to (define it 'dummy) then it works. I had a hunch that ' ie. quote of (go 'dummy) that is bound by #lang racket is not recognized as the same binding within the submodule mod even though syntactically it is the same sequence of letters. If I strip 'dummy of all lexical context by round tripping as follows:

(with-syntax ([ok (datum->syntax #f (syntax->datum #'id))])

below the pattern match (_ id) and replace definition of it with (define it ok) then all is good again.

#lang racket
(define-syntax (go stx)
  (syntax-case stx ()
    [(_ id)
     (with-syntax ([ok (datum->syntax #f (syntax->datum #'id))])
       #'(module mod racket
           (define it ok)))]
    ))
(go 'dummy)

I presume that my dilemma was caused by the hygiene system. However, is there a more direct solution to convince racket compiler that these identifiers, ie. quote are really the same without this boilerplate?


Solution

  • The expression that you insert for id in:

    (module mod racket
      (define it id))
    

    is going to be evaluated in the context of the module. Therefore the syntactic context id id needs to be the same as the context of the submodule.

    You describe one way of removing existing context. Here is another:

    #lang racket
    (require (for-syntax racket/base))
    
    (define-syntax (go stx)
      (syntax-case stx ()
        [(_ id)
         (with-syntax ([id (syntax->datum #'id)])
           #'(module mod racket
               (provide it)
               (define it id)))]))
    
    (go 42)
    (require (submod "." mod))
    it
    

    In most macros it is a good thing that context is preserved, so having to "boiler plate" to remove it seems okay to me.

    Of course, if you experience to much boiler plate then write a macro that inserts the boiler plate for you :-)