Ond*_*rej 3 macros hygiene racket
测试代码:
(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
Run Code Online (Sandbox Code Playgroud)
我不明白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)))))
Run Code Online (Sandbox Code Playgroud)
我想,在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))
Run Code Online (Sandbox Code Playgroud)
define: not allowed in an expression context
Run Code Online (Sandbox Code Playgroud)
你可以通过对如何打破卫生更加保守来避免这个问题.真的,在这种情况下,我们想要不卫生的宏的唯一部分是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))
Run Code Online (Sandbox Code Playgroud)
现在该程序正常运行,它只需要在一个地方不卫生.