我正试图从Common Lisp迁移到Chicken Scheme,并且遇到很多问题。
我目前的问题是:如何编写调用其他宏的宏(大概使用define-syntax
?)?
例如,在Common Lisp中,我可以这样做:
(defmacro append-to (var value)
`(setf ,var (append ,var ,value)))
(defmacro something-else ()
(let ((values (list))
(append-to values '(1)))))
而在Scheme中,等效代码不起作用:
(define-syntax append-to
(syntax-rules ()
((_ var value)
(set! var (append var value)))))
(define-syntax something-else
(syntax-rules ()
((_)
(let ((values (list)))
(append-to values '(1))))))
无法从append-to
宏调用something-else
宏。我收到一条错误消息,指出append-to
“变量”未定义。
根据我设法从Google和其他来源收集的所有信息,宏在无法访问其他代码的封闭环境中进行评估。基本上,除了内置的Scheme函数和宏之外,没有其他任何东西存在 - 当评估宏时。我尝试过使用er-macro-transformer
,syntax-case
(现在已经在Chicken中弃用)甚至是procedural-macros
模块。
宏的完整目的当然是它们构建在其他宏之上,以避免重复代码。如果宏必须单独编写,那么在我看来它们几乎没用。
我已经调查了其他Scheme实现,并且没有更多的运气。似乎根本无法完成。
有人可以帮帮我吗?
答案 0 :(得分:2)
看起来您将扩展时间与运行时混淆。您提供的syntax-rules
示例将展开到let + set,这意味着追加将在运行时发生。
syntax-rules
只是将输入重写为给定的输出,扩展宏直到没有其他扩展。如果你想在扩展时实际执行一些计算,唯一的方法就是使用一个过程宏(这也是你defmacro
CL例子中发生的事情)。
在Scheme中,评估级别是严格分开的(这使得单独的编译成为可能),因此一个过程可以使用宏,但宏本身不能使用在同一段代码中定义的过程(或宏)。您可以使用use-for-syntax从模块加载过程和宏以在过程宏中使用。通过将它们包装在begin-for-syntax中来定义在语法扩展时运行的东西的支持有限。
例如,请参阅this SO question或this discussion on the ikarus-users mailing list。 Matthew Flatt的论文composable and compilable macros更详细地解释了这背后的理论。
“阶段分离”思想在Scheme世界中相对较新(注意Flatt论文是从2002年开始的),因此你会发现Scheme社区中有不少人对此仍感到有些困惑。它是“新”的原因(即使Scheme已经有很长时间的宏)是因为程序宏只是自R6RS以来已经成为标准的一部分(并且因为syntax-case
在R7RS中还原而备受争议),所以需要严格指定它们直到现在才成为问题。对于Scheme的更多“传统”Lispy实现,其中编译时和运行时都被混合在一起,这从来都不是问题;你可以随时运行代码。
要回到您的示例,如果您正确分离阶段,它可以正常工作:
(begin-for-syntax
(define-syntax append-to
(ir-macro-transformer
(lambda (e i c)
(let ((var (cadr e))
(val (caddr e)))
`(set! ,var (append ,var ,val)))))) )
(define-syntax something-else
(ir-macro-transformer
(lambda (e i c)
(let ((vals (list 'print)))
(append-to vals '(1))
vals))))
(something-else) ; Expands to (print 1)
如果您将append-to
的定义放在自己的模块中,并且use-for-syntax
,那么它也可以正常工作。这也允许您在代码体和程序中定义的宏中使用相同的模块,只需在use
和 a {use-for-syntax
和{ {1}}表达。