Suzanne Soy
Suzanne Soy

Reputation: 3244

Racket: `syntax-local-introduce` across module boundary

In the file below, the macro m2 defines an identifier foo, and makes it available to the user code via syntax-local-introduce. When subsequently expanding m1, (free-identifier=? #'m2-id #'user-id) evaluates to #t as expected.

#lang racket

(define-syntax (m1 stx)
  (syntax-case stx ()
    [(_ m2-id user-id)
     #`#,(free-identifier=? #'m2-id #'user-id)]))

(define-syntax (m2 stx)
  (syntax-case stx ()
    [(_ user-id)
     #`(begin (define (foo) 1)
              (m1 foo #,(syntax-local-introduce #'user-id)))]))

(m2 foo) ;; => #t
(m2 foo) ;; => #t

However, if I wrap the two macros in a module, (free-identifier=? #'m2-id #'user-id) evaluates to #f.

#lang racket

(module m racket
  (define-syntax (m1 stx)
    (syntax-case stx ()
      [(_ m2-id user-id)
       #`#,(free-identifier=? #'m2-id #'user-id)]))

  (define-syntax (m2 stx)
    (syntax-case stx ()
      [(_ user-id)
       #`(begin (define (foo) 1)
                (m1 foo #,(syntax-local-introduce #'user-id)))]))

  (provide m2))

(require 'm)

(m2 foo) ;; => #f
(m2 foo) ;; => #f

Why is this happening?

How can I solve this and obtain the first example's behaviour when the macros are defined in a separate module?

Please note that I want to be able to execute the macro more than once, so using (define (#,(datum->syntax stx 'foo)) 1) is not an option, as this would result in conflicting definitions when the macro is called twice.

I tried using ((make-syntax-delta-introducer #'foo #'user-id) #'user-id) instead of #,(syntax-local-introduce #'user-id), but it doesn't work either.

Actual code where I'm using this

Here is (in pseudocode) the actual code I'm writing:

(define-syntax (define* stx)
  ... expand types, uses free-identifier=? ...)

(define-syntax (define-function-group stx)
    (syntax-parse stx
      [(_ ((~literal defun) (f-name arg ...) ret-type body) ...)
       #`(begin (define-type-expander (return-type-of stx)
                  (syntax-parse stx
                    [(_ (~literal f-name)) #'ret-type] ...))
                (define* (f-name arg ...) : ret-type
                  body) ...)]))

;; defines a type expander "return-type-of"
(define-function-group
  (defun (f1) (Listof String) '("a" "b"))
  (defun (f2 [a : (return-type-of f1)] (length '("a" "b")))))

;; defines another type expander "return-type-of"
(define-function-group etc.)

Where define-type-expander is from my type-expander library and works like match-expanders (I should package it at some point, but it still needs some work). define* (which corresponds to m1 above) is a variant of define* that expands types, and it uses free-identifier=? at some point to find the right type-expander definition. define-function-group (which corresponds fo m2 above) allows referring to the return type of another function, using (return-type-of some-function-name). The thing is that each call to define-function-group introduces a new copy of return-type-of, so it needs to have the macro's scope to avoid conflicts.

Upvotes: 2

Views: 160

Answers (1)

belph
belph

Reputation: 362

There might be a more hygienic way of doing it, but this is what I came up with:

You say that datum->syntax isn't an option, but consider the following:

(define-syntax (m2 stx)
    (syntax-case stx ()
      [(_ user-id)
       (with-syntax ([foo (syntax-local-introduce (datum->syntax stx 'foo))])
       #`(begin (define (foo) 1)
                (m1 foo #,(syntax-local-introduce #'user-id))))]))

While this uses datum->syntax, it also adds the use-site scopes to foo, making the identifier unique to each macro expansion. If you replace m2 with the above in your example code, everything runs as desired (and, as you predict, removing syntax-local-introduce causes duplicate definition errors).

Upvotes: 1

Related Questions