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