datum->语法和语法之间有什么区别#'在define-syntax体中?

时间:2018-04-05 09:43:27

标签: macros racket hygiene

测试代码:

(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-estx中传递的信息(上下文?)。

如何使用#'定义callme。仅?

1 个答案:

答案 0 :(得分:6)

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

但是,在您的情况下,您需要明确 unhygienic 的行为。您希望宏定义新标识符并使​​该标识符位于宏之外的范围内。幸运的是,虽然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))

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

相关问题