Reputation: 3244
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.
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
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