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

How to call other macros from a Chicken Scheme macro?

我正在尝试从 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))))))

无法从 something-else 宏调用 append-to 宏。我收到一条错误消息,指出 append-to "variable" 未定义。

根据我设法从 Google 和其他来源收集到的所有信息,宏是在封闭环境中评估的,无法访问其他代码。本质上,当宏被求值时,除了内置的 Scheme 函数和宏之外,没有别的东西存在。我尝试过使用 er-macro-transformersyntax-case(现在在 Chicken 中已弃用)甚至 procedural-macros 模块。

当然,宏的全部目的 是它们建立在其他 宏之上,以避免重复代码。如果必须单独编写宏,那么在我看来,它们几乎毫无用处。

我研究了其他 Scheme 实现,但没有更多的运气。看来根本做不到。

有人可以帮我解决这个问题吗?

您似乎混淆了扩展时间和 运行 时间。您给出的 syntax-rules 示例将 扩展 到 let+set,这意味着追加将在 运行 时间发生。

syntax-rules 只是将输入重写为给定的输出,扩展宏直到没有更多内容可以扩展。如果您想在扩展时实际执行一些计算,唯一的方法是使用过程宏(这也是您的 defmacro CL 示例中发生的情况)。

在Scheme中,求值层次是严格分开的(这使得单独编译成为可能),所以一个过程可以使用宏,但是宏本身不能使用同一段代码中定义的过程(或宏)。您可以使用 use-for-syntax. There's limited support for defining things to run at syntax expansion time by wrapping them in begin-for-syntax.

从模块加载过程和宏以在过程宏中使用

参见示例 this SO question or this discussion on the ikarus-users mailing list. Matthew Flatt's paper composable and compilable macros 更详细地解释了其背后的理论。

"phase separation" 思想在 Scheme 世界中相对较新(请注意 Flatt 论文是 2002 年的),所以你会发现 Scheme 社区中的很多人仍然对它。它是 "new" 的原因(尽管 Scheme 已经有很长时间的宏了)是因为过程宏从 R6RS 开始才成为标准的一部分(并在 R7RS 中恢复,因为 syntax-case 相当有争议),所以严格指定它们的需求直到现在才成为问题。对于 Scheme 的更多 "traditional" Lispy 实现,编译时和 运行-time 都混合在一起,这从来都不是问题;您可以随时 运行 编码。

回到你的例子,如果你正确地分开阶段,它工作得很好:

(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 和 [=38= 中都要求它]一个use-for-syntax表达式。