【问题标题】:What is difference between datum->syntax and syntax #' in define-syntax body?datum->syntax 和 define-syntax body 中的语法 #' 有什么区别?
【发布时间】:2018-09-15 02:03:52
【问题描述】:

测试代码:

(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 也仅使用 #' 定义?

【问题讨论】:

    标签: macros racket hygiene


    【解决方案1】:

    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))
    

    现在这个程序可以工作了,它只是在它需要的地方不卫生。

    【讨论】:

      猜你喜欢
      • 2020-07-24
      • 2020-07-24
      • 2016-01-11
      • 2010-09-12
      • 2018-07-31
      • 2015-11-10
      • 2018-12-21
      • 1970-01-01
      • 2015-10-24
      相关资源
      最近更新 更多