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

8

测试代码:

(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缺少通过stx传递的一些信息(上下文?)。

如何使callme也可以仅使用#符号定义?

1个回答

9
Racket的宏系统是“卫生的”。这意味着由宏引入的标识符存在于其自己的作用域中,不会与在宏外部使用或定义的标识符发生冲突。通常情况下,这是您想要的,因为它避免了当宏作者和宏用户都决定使用相同的变量名时出现问题。
然而,在您的情况下,您需要明确“非卫生”的行为。您希望宏定义一个新的标识符,并使该标识符在宏外部范围内。幸运的是,尽管Racket默认实施卫生,但在需要时可以打破(或“弯曲”)卫生。
当您使用#',也称为语法,您正在使用卫生宏功能。这意味着您对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))

现在程序可以运行了,只是有一个需要改进的地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接