测试代码:
(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
。仅?
答案 0 :(得分:6)
Racket的宏系统卫生。这意味着宏引入的标识符存在于它们自己的范围内 - 它们不会与宏之外使用或定义的标识符冲突。这通常是您想要的,因为当宏作者和宏用户都决定使用相同的变量名时,它可以避免出现问题。
但是,在您的情况下,您需要明确 unhygienic 的行为。您希望宏定义新标识符并使该标识符位于宏之外的范围内。幸运的是,虽然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))
现在该程序正常运行,并且它只需要在一个地方不卫生。