define-syntax body 中的 datum->syntax 和 syntax #' 有什么区别?
What is difference between datum->syntax and syntax #' in define-syntax body?
测试代码:
(define-syntax (test-d stx)
#'(begin
(define (callme a)
(writeln a))))
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
> (test-d)
> (callme 1)
. . callme: undefined;
cannot reference an identifier before its definition
> (test-e)
> (callme2 1)
1
我不明白 test-d 和 test-e 的区别。对我来说,他们看起来是平等的。不过,callme 还没有定义。
连宏步进器都说是一样的
Expansion finished
(module anonymous-module racket
(#%module-begin
(define-syntax (test-d stx)
#'(begin (define (callme a) (writeln a))))
(define-syntax (test-e stx)
(datum->syntax
stx
'(begin (define (callme2 a) (writeln a)))))
(begin (define (callme a) (writeln a)))
(begin (define (callme2 a) (writeln a)))))
我猜 test-d
中缺少一些在 test-e
中通过 stx
传递的信息(上下文?)。
如何仅使用 #' 也能定义 callme
?
Racket 的宏观系统卫生。这意味着宏引入的标识符存在于它们自己的范围内——它们不会与在宏之外使用或定义的标识符发生冲突。这通常是您想要的,因为它避免了宏作者和宏用户决定使用相同变量名时出现的问题。
但是,在您的情况下,您想要明显不卫生 的行为。您希望宏定义一个新的标识符并使该标识符位于宏之外的范围内。幸运的是,虽然 Racket 默认强制执行卫生,但它允许您在需要时打破(或“弯曲”)卫生。
当您使用 #'
,也就是 syntax
时,您使用的是卫生宏功能。这意味着您对 callme
的定义仅在 test-d
内部可见,并且对调用代码不可见。但是,datum->syntax
是允许您打破卫生的主要机制之一:它“伪造”了一个新的语法片段,该语法片段与另一个语法片段处于相同的范围内,在您的例子中是 stx
,这是宏的输入。这就是为什么 callme2
在 test-e
的定义之外可见。
然而,这是一把重锤……事实上太重了。您的 test-e
宏 残酷地 不卫生,这意味着如果宏的用户绑定了 test-e
使用的名称,它可能会被破坏。例如,如果用户定义了一个名为 begin
的局部变量,test-e
将不再起作用:
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
define: not allowed in an expression context
您可以通过更加保守地破坏卫生来避免这个问题。实际上,在这种情况下,我们唯一希望不卫生的宏是 callme2
标识符,因此我们可以使用 datum->syntax
伪造该语法,但对所有宏都使用 #'
其余:
(define-syntax (test-e stx)
(with-syntax ([callme-id (datum->syntax stx 'callme2)])
#'(begin
(define (callme-id a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
现在程序可以运行了,只是在需要的地方不卫生。
测试代码:
(define-syntax (test-d stx)
#'(begin
(define (callme a)
(writeln a))))
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
> (test-d)
> (callme 1)
. . callme: undefined;
cannot reference an identifier before its definition
> (test-e)
> (callme2 1)
1
我不明白 test-d 和 test-e 的区别。对我来说,他们看起来是平等的。不过,callme 还没有定义。
连宏步进器都说是一样的
Expansion finished
(module anonymous-module racket
(#%module-begin
(define-syntax (test-d stx)
#'(begin (define (callme a) (writeln a))))
(define-syntax (test-e stx)
(datum->syntax
stx
'(begin (define (callme2 a) (writeln a)))))
(begin (define (callme a) (writeln a)))
(begin (define (callme2 a) (writeln a)))))
我猜 test-d
中缺少一些在 test-e
中通过 stx
传递的信息(上下文?)。
如何仅使用 #' 也能定义 callme
?
Racket 的宏观系统卫生。这意味着宏引入的标识符存在于它们自己的范围内——它们不会与在宏之外使用或定义的标识符发生冲突。这通常是您想要的,因为它避免了宏作者和宏用户决定使用相同变量名时出现的问题。
但是,在您的情况下,您想要明显不卫生 的行为。您希望宏定义一个新的标识符并使该标识符位于宏之外的范围内。幸运的是,虽然 Racket 默认强制执行卫生,但它允许您在需要时打破(或“弯曲”)卫生。
当您使用 #'
,也就是 syntax
时,您使用的是卫生宏功能。这意味着您对 callme
的定义仅在 test-d
内部可见,并且对调用代码不可见。但是,datum->syntax
是允许您打破卫生的主要机制之一:它“伪造”了一个新的语法片段,该语法片段与另一个语法片段处于相同的范围内,在您的例子中是 stx
,这是宏的输入。这就是为什么 callme2
在 test-e
的定义之外可见。
然而,这是一把重锤……事实上太重了。您的 test-e
宏 残酷地 不卫生,这意味着如果宏的用户绑定了 test-e
使用的名称,它可能会被破坏。例如,如果用户定义了一个名为 begin
的局部变量,test-e
将不再起作用:
(define-syntax (test-e stx)
(datum->syntax stx '(begin
(define (callme2 a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
define: not allowed in an expression context
您可以通过更加保守地破坏卫生来避免这个问题。实际上,在这种情况下,我们唯一希望不卫生的宏是 callme2
标识符,因此我们可以使用 datum->syntax
伪造该语法,但对所有宏都使用 #'
其余:
(define-syntax (test-e stx)
(with-syntax ([callme-id (datum->syntax stx 'callme2)])
#'(begin
(define (callme-id a)
(writeln a)))))
(let ([begin 42])
(test-e)
(callme2 1))
现在程序可以运行了,只是在需要的地方不卫生。