定义语法主体中的基准>语法和语法 #' 有什么区别?



测试代码:

(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

我不了解测试-D和测试-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

球拍的宏系统是卫生。这意味着宏观现场引入的标识符在自己的范围内 - 它们不会与宏外使用或定义的标识符发生碰撞。这通常是您想要的,因为当宏作者和宏用户决定使用相同的变量名称时,它会避免出现问题。

在您的情况下,您需要明确的不卫生的行为。您希望宏来定义一个新的标识符,并在宏之外具有标识符在范围内。幸运的是,虽然球拍默认会实施卫生,但它允许您在需要时打破(或"弯曲"(卫生。

使用#'(又称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))

现在该程序有效,它在需要的一个地方只有不卫生。