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,这是宏的输入。这就是为什么 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))
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))

现在程序可以运行了,只是在需要的地方不卫生。