在下面的文件中,宏m2
定义了标识符foo
,并通过syntax-local-introduce
将其提供给用户代码。随后展开m1
时,(free-identifier=? #'m2-id #'user-id)
按预期评估为#t
。
#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
但是,如果我将两个宏包装在一个模块中,(free-identifier=? #'m2-id #'user-id)
的计算结果为#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
为什么会这样?
如何在单独的模块中定义宏时,如何解决此问题并获取第一个示例的行为?
请注意,我希望能够多次执行宏,因此使用(define (#,(datum->syntax stx 'foo)) 1)
不是一个选项,因为当调用宏两次时,这会导致定义冲突。
我尝试使用((make-syntax-delta-introducer #'foo #'user-id) #'user-id)
代替#,(syntax-local-introduce #'user-id)
,但它也不起作用。
这是(在伪代码中)我写的实际代码:
(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.)
define-type-expander
来自我的类型扩展器库并且像匹配扩展器一样工作(我应该在某些时候打包它,但它仍然需要一些工作)。 define*
(对应上面的m1
)是扩展类型的define*
的变体,它在某些时候使用free-identifier=?
来查找正确的类型 - 扩展器定义。 define-function-group
(对应上面的m2
)允许使用(return-type-of some-function-name)
引用另一个函数的返回类型。问题在于,每次调用define-function-group
都会引入return-type-of
的新副本,因此需要具有宏的范围以避免冲突。
答案 0 :(得分:1)
可能有一种更卫生的方式,但这就是我想出来的:
您说datum->syntax
不是一个选项,但请考虑以下因素:
(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))))]))
虽然它使用datum->syntax
,但它还将使用地点范围添加到foo
,使标识符对每个宏扩展都是唯一的。如果您在示例代码中将m2
替换为上述代码,则所有内容都会根据需要运行(并且正如您所预测的那样,删除syntax-local-introduce
会导致重复的定义错误。)