测试代码:
(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
我不了解测试-D和测试-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
球拍的宏系统是卫生。这意味着宏观现场引入的标识符在自己的范围内 - 它们不会与宏外使用或定义的标识符发生碰撞。这通常是您想要的,因为当宏作者和宏用户决定使用相同的变量名称时,它会避免出现问题。
在您的情况下,您需要明确的不卫生的行为。您希望宏来定义一个新的标识符,并在宏之外具有标识符在范围内。幸运的是,虽然球拍默认会实施卫生,但它允许您在需要时打破(或"弯曲"(卫生。
使用#'
(又称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))
现在该程序有效,它在需要的一个地方只有不卫生。