假设我想在编译时替换一个过程的所有出现,例如,将cons
的所有出现替换为😄
。我尝试了两个看起来很自然的选项:
1。
(define-syntax 😄
(syntax-rules ()
[(_ x y) (cons x y)]))
如果我做像(😄 2 3)
这样的事情,但我不能使用😄
,如果它不在应用程序位置,所以如果我做(map 😄 '(1 2) '(3 4))
,我会得到一个"坏语法"错误。
然后我想只是替换标识符本身:
2。
(define-syntax 😄
(λ (_) #'cons))
现在😄
本身给了我#<procedure:cons>
和(map 😄 '(1 2) '(3 4))
给出了正确的答案,但是对于(😄 2 3)
,语法转换器正在接受所有参数并将整个表达式替换为cons
,这不是我想要的。
我如何实现我想要的?有没有什么变压器能做到这一点?
更新:当我输入最后一句话时,我调用了"橡皮鸭效应",找到了make-rename-transformer
,这就是我想要的东西。然而,文档说"这样的转换器可以手动编写",似乎我没有这样做与我的2次尝试。如何手工制作这样的变压器?(忽略make-syntax-transformer
文档中的项目符号)
同时,我知道我可以只使用(define 😄 cons)
,但那是一个完整的额外的函数调用在运行时
最简单(可能也是最好)的方法是使用rename-in
以不同的名称导入标识符:
(require (rename-in racket/base [cons 😄]))
这也是最直接的方法,因为它根本不会创建语法转换器——它将创建一个在各个方面都与cons
相同的新绑定,包括free-identifier=?
。从编译器的角度来看,这使得cons
和😄
无法区分。
然而,这有点神奇。另一种方法是使用make-rename-transformer
:
(define-syntax 😄 (make-rename-transformer #'cons))
如果不能使用rename-in
,则使用重命名转换器是次佳选择,因为free-identifier=?
函数专门识别重命名转换器,因此(free-identifier=? #'cons #'😄)
仍将是#t
。这对于关心语法绑定的宏很有用,因为😄
将在所有cons
可以接受的地方被接受。
仍然是在使用某种原语。如果你真的想这么做,你怎么能自己实现make-rename-transformer
呢?这里的关键是宏应用程序可以在Racket中以两种形式出现。如果您有一个宏foo
,那么它可以以以下两种方式使用:
foo
(foo ...)
第一种情况是" id宏",当宏用作裸标识符时,第二种是更常见的情况。但是,如果您想编写一个像表达式一样工作的宏,则需要考虑这两种情况。syntax-rules
不能这样做,它不允许使用id宏,但是您可以使用syntax-id-rules
:
(define-syntax 😄
(syntax-id-rules ()
[(_ . args) (cons . args)]
[_ cons]))
此模式将使😄
在两个场景中按预期展开。然而,与上述两种方法不同的是,它将不与free-identifier=?
合作,因为从宏扩展器的角度来看,😄
现在是一个绑定到普通宏的全新绑定。
从这个角度来看,make-rename-transformer
是神奇的吗?不完全是,但它是特殊的,因为free-identifier=?
处理它作为一个特殊情况。如果您愿意,您可以实现自己的make-rename-transformer
函数和自己的free-identifier=?
函数来获得类似的行为:
(begin-for-syntax
(struct my-rename-transformer (id-stx)
#:property prop:procedure
(λ (self stx)
(with-syntax ([id (my-rename-transformer-id-stx self)])
((set!-transformer-procedure
(syntax-id-rules ()
[(_ . args) (id . args)]
[_ id]))
stx))))
(define (my-rename-target id-stx)
(let ([val (syntax-local-value id-stx (λ () #f))])
(if (my-rename-transformer? val)
(my-rename-target (my-rename-transformer-id-stx val))
id-stx)))
(define (my-free-identifier=? id-a id-b)
(free-identifier=? (my-rename-target id-a)
(my-rename-target id-b))))
你可以这样做:
(define-syntax 😄 (my-rename-transformer #'cons))
…和(my-free-identifier=? #'😄 #'cons)
将是#t
。但是,不建议您实际上这样做,原因很明显:它不仅没有必要,而且不会真正起作用,因为其他宏不会使用my-free-identifier=?
,只使用普通的free-identifier=?
。因此,强烈建议您使用make-rename-transformer
。
如果您想手动编写这样的宏,您需要使用make-set!-transformer
。
http://docs.racket-lang.org/reference/stxtrans.html?q=set-transformer % 28 def。 % 28% 28 " ~ 23 ~ 25内核% 29. _make-set % 21-transformer % 29% 29日
请注意,您需要处理赋值(set! x e)
和引用x
,而应用程序(x arg0 arg1 ...)
需要由syntax-case
表达式中的子句处理。
在Dybvig的书"The Scheme Programming Language"中,他有一个宏define-integrable
的例子,听起来就是你要找的东西。
http://www.scheme.com/tspl4/syntax.html。/语法:s61