如何从Chicken Scheme宏调用其他宏?

时间:2016-08-08 01:39:16

标签: macros scheme chicken-scheme define-syntax

我正试图从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-transformersyntax-case(现在已经在Chicken中弃用)甚至是procedural-macros模块。

宏的完整目的当然是它们构建在其他宏之上,以避免重复代码。如果宏必须单独编写,那么在我看来它们几乎没用。

我已经调查了其他Scheme实现,并且没有更多的运气。似乎根本无法完成。

有人可以帮帮我吗?

1 个答案:

答案 0 :(得分:2)

看起来您将扩展时间与运行时混淆。您提供的syntax-rules示例将展开到let + set,这意味着追加将在运行时发生。

syntax-rules只是将输入重写为给定的输出,扩展宏直到没有其他扩展。如果你想在扩展时实际执行一些计算,唯一的方法就是使用一个过程宏(这也是你defmacro CL例子中发生的事情)。

在Scheme中,评估级别是严格分开的(这使得单独的编译成为可能),因此一个过程可以使用宏,但宏本身不能使用在同一段代码中定义的过程(或宏)。您可以使用use-for-syntax从模块加载过程和宏以在过程宏中使用。通过将它们包装在begin-for-syntax中来定义在语法扩展时运行的东西的支持有限。

例如,请参阅this SO questionthis 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}}表达。