在define-syntax体中,datum-> syntax和syntax#'有什么区别?

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只使用#' 来定义?

Ale*_*ing 6

Racket的宏观系统很卫生.这意味着宏引入的标识符存在于它们自己的范围内 - 它们不会与宏之外使用或定义的标识符冲突.这通常是您想要的,因为当宏作者和宏用户都决定使用相同的变量名时,它可以避免出现问题.

但是,在您的情况下,您需要明确不卫生的行为.您希望宏定义新标识符并使​​该标识符位于宏之外的范围内.幸运的是,虽然Racket默认执行卫生,但它允许您在需要时打破(或"弯曲")卫生.

当您使用#'syntax,您也使用卫生宏功能.这意味着您的定义callme仅在内部可见test-d,并且对于调用代码将不可见.然而,它datum->syntax是允许您破坏卫生的主要机制之一:它"伪造"一个新的语法,与您的情况下的另一段语法位于同一范围内stx,这是宏的输入.这就是为什么callme2test-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)

现在该程序正常运行,它只需要在一个地方不卫生.