测试代码:
(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呢?
发布于 2018-04-05 16:43:03
球拍的宏观系统是卫生的。这意味着宏引入的标识符存在于它们自己的范围内--它们不会与在宏之外使用或定义的标识符发生冲突。这通常是您想要的,因为它避免了当宏作者和宏用户决定使用相同的变量名时出现的问题。
但是,在您的情况下,您需要的行为显然是不卫生的。您希望宏定义一个新的标识符,并使该标识符位于宏之外的作用域中。幸运的是,虽然球拍在默认情况下强制卫生,但它允许你打破(或“弯曲”)卫生,当你想。
当您使用#',又名syntax时,您使用的是卫生的宏特性。这意味着您对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))现在这个程序起作用了,它只是在一个需要的地方不卫生。
https://stackoverflow.com/questions/49669142
复制相似问题