关于宏如何在Scheme中工作(特别是鸡计划),我有一些问题,让我们考虑这个例子:
(define (when-a condition . body)
(eval `(if ,condition
(begin ,@body)
'())))
(define-syntax when-b
(er-macro-transformer
(lambda (exp rename compare)
(let ((condition (cadr exp))
(body (cddr exp)))
`(if ,condition (begin ,@body) '())))))
(define-syntax when-c
(ir-macro-transformer
(lambda (exp inject compare)
(let ((condition (cadr exp))
(body (cddr exp)))
`(if ,condition (begin ,@body) '())))))
(define-syntax when-d
(syntax-rules ()
((when condition body ...)
(if condition (begin body ...) '()))))
我可以考虑when-a
一个宏吗?我觉得我不能严格地认为这是一个宏,因为我没有使用define-syntax
,但我不能说任何不喜欢这种实现的实际理由。
我的宏是否卫生?
when-b
和when-c
之间有什么区别吗?由于我没有使用rename
或inject
,我认为没有。
答案 0 :(得分:3)
我可以考虑何时 - 一个宏?我觉得我不能严格地将它视为一个宏,因为我没有使用define-syntax,但是我不能说任何不喜欢这种实现的实际理由。
这就像一个宏,但由于以下原因,它与真正的宏不完全相同:
(if #f (error "oops") '())
将评估为'()
,但(when-a #f (error "oops"))
会引发错误。(eval '(define if "not a procedure"))
之类的事情,这意味着这个eval会失败; "扩展"的正文表达式中的if
不会在定义网站上引用if
。由于我的宏是否卫生?
when-c
和when-d
提供的保证,仅ir-macro-transformer
和syntax-rules
。在when-b
中,您必须重命名if
和begin
,以便在宏定义网站上引用if
和begin
的版本。
示例:
(let ((if #f))
(when-b #t (print "Yeah, ok")))
== expands to ==>
(let ((if1 #f))
(if1 #t (begin1 (print "Yeah, ok"))))
这会失败,因为if
的两个版本(此处带有额外的1
后缀注释)引用相同的内容,因此我们最终会调用#f
操作员职位。
相比之下,
(let ((if #f))
(when-c #t (print "Yeah, ok")))
== expands to ==>
(let ((if1 #f))
(if2 #t (begin1 (print "Yeah, ok"))))
哪个可以按预期工作。如果你想重写when-b
是卫生的,那就这样做:
(define-syntax when-b
(er-macro-transformer
(lambda (exp rename compare)
(let ((condition (cadr exp))
(body (cddr exp))
(%if (rename 'if))
(%begin (rename 'begin)))
`(,%if ,condition (,%begin ,@body) '())))))
请注意额外的%
- 带前缀的标识符,这些标识符引用if
和begin
的原始值,因为它们位于宏的定义位置。
when-b和when-c之间有什么区别吗?由于我不使用重命名或注入,我认为没有。
有。 隐式重命名宏被调用,因为它们隐式重命名来自使用站点的所有标识符,以及您在正文中引入的每个新标识符。如果您注入任何标识符,则撤消此隐式重命名,这使得它们不受干扰地被调用代码捕获。
另一方面,调用显式重命名宏,因为您必须显式重命名任何标识符以防止它们被调用代码捕获。