6

测试代码:

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

我猜想 intest-d缺少一些test-e通过stx.

我怎样才能实现callme仅使用 #' 来定义?

4

1 回答 1

8

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

In your case, however, you want behavior that is explicitly unhygienic. You want the macro to define a fresh identifier and have that identifier be in scope outside of the macro. Fortunately, while Racket enforces hygiene by default, it allows you to break (or “bend”) hygiene when you want to.

When you use #', aka syntax, you are using hygienic macro features. This means that your definition of callme is only visible inside of test-d, and it won’t be visible to the calling code. However, datum->syntax is one of the primary mechanisms that allows you to break hygiene: it “forges” a new piece of syntax that lives in the same scope as another piece of syntax, in your case stx, which is the input to the macro. This is why callme2 is visible outside of test-e’s definition.

However, this is a heavy hammer… too heavy, in fact. Your test-e macro is brutally unhygienic, and this means it can be broken if the user of the macro binds a name used by test-e. For example, if the user defines a local variable named begin, test-e won’t work anymore:

(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

You can avoid this problem by being more conservative about how you break hygiene. Really, in this situation, the only piece of the macro we want to be unhygienic is the callme2 identifier, so we can forge that piece of syntax using datum->syntax, but use #' for all the rest:

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

Now the program works, and it is only unhygienic in the one spot that it needs to be.

于 2018-04-05T16:43:03.327 回答