Lisp或Scheme中是否有整个程序转换的宏?

6
我看到了一个stackoverflow问题的答案(由Noah Lavine回答):Lisp如何让您重新定义语言本身?。宏不是完全重新定义语言,至少在我所知道的情况下(我实际上是个Schemer,可能错了),因为有一些限制。宏只能接受代码的单个子树,然后生成一个用于替换它的单个子树。因此,您无法编写整个程序转换的宏,尽管这很酷。阅读完这篇文章后,我想知道是否有“整个程序转换的宏”在Lisp或Scheme(或其他语言)中可用。如果没有,那么为什么?有以下几种可能:它不是有用的或者从不需要的、同样的事情可以通过其他方式实现、即使在Lisp中也无法实现它、它是可能的,但从未尝试过或从未实现过。更新:一种使用情况,例如stumpwm代码,其中有一些函数都在不同的lisp源文件中使用一个动态/全局defvar变量*screen-list*,该变量在primitives.lisp中定义,但在screen.lisp、user.lisp和window.lisp中使用。现在,我想将这些函数定义在可以访问let表单的闭包中,而不是动态/全局变量,但是不想将所有这些函数移到同一个位置(因为我不希望这些函数从它们相关的文件中消失),以便只有这些函数才能访问该变量。以上示例同样适用于label和flet,因此进一步可能使仅对需要它的人可用所需的变量和函数。请注意,除了使用第一个参数为上下文的defun_with_context宏之外,还可以通过读取器宏来实现,如Vatine和Gareth Rees所回答的那样。
7个回答

5
在Racket中,你可以实现整个程序转换的宏。请参阅文档中关于定义新语言的部分。在Racket中有许多这样的例子,例如lazy语言和Typed Racket。

5

你引用了Noah Lavine的话:

宏只能获取您代码的单个子树,并生成一个子树来替换它

这适用于普通宏,但是读取器宏可以访问输入流并对其进行任何操作。

请参见Hyperspec 第2.2节set-macro-character函数。


2
从我的记忆中,有几种方法:
首先,您可以。Norvig指出

我们可以将编译器编写为一组宏。

因此,如果您想要的话,可以转换整个程序。我只看到过很少这样做,因为通常“您想对程序的每个部分执行的操作”和“您需要使用宏/ AST类型转换的操作”之间的交集是一个相当小的集合。一个例子是Parenscript,它将您的Lisp代码(“CL的扩展子集”)转换为Javascript。我曾经用它将整个Lisp代码文件编译成直接提供给Web客户端的Javascript。这不是我最喜欢的环境,但它确实实现了它所宣传的功能。
另一个相关特性是“advice”,Yegge描述道
伟大的系统也有建议。这个功能没有普遍接受的名称。有时它被称为钩子、过滤器或面向方面的编程。据我所知,Lisp 最先使用了它,并且在 Lisp 中它被称为 advice。Advice 是一个迷你框架,通过它你可以编程地修改系统中某个动作或函数调用的行为,提供了 before、around 和 after 钩子。
另一个是特殊变量。通常,宏(和其他结构)适用于词法作用域。通过声明一个变量为特殊变量,你告诉它应用于动态作用域(我认为它是“时间作用域”)。我想不出还有哪种语言让你(程序员)在这两者之间选择。除了编译器的情况外,这两种情况确实涵盖了我作为程序员感兴趣的领域。

1

一个典型的方法是编写自己的模块系统。如果你只想访问所有代码,可以使用某种预处理器或读取器扩展来包装源文件,加上自己的模块注释。然后编写自己的requireimport形式,最终能够看到所有作用域内的代码。

要开始,您可以编写自己的module形式,让您定义几个函数,然后以某种聪明的方式编译它们,然后发出优化的代码。


0
在Common LISP中,您可以使用PROGN包装顶层表单,并仍然保留它们作为顶层表单的状态(请参见CLTL2,第5.3节)。因此,宏生成单个子树的限制并不是很大的限制,因为它可以在PROGN中包装任意数量的结果子树。这使得整个程序宏成为可能。
例如。
(my-whole-program-macro ...)

= expands to =>

(progn
  (load-system ...)
  (defvar ...)
  (defconstant ...)
  (defmacro ...)
  (defclass ...)
  (defstruct ...)
  (defun ...)
  (defun ...)
  ...
  )

0

总是可以选择使用编译器宏(它们可以根据一系列标准进行整个函数转换,但不应更改返回的值,因为那会令人困惑)。

还有阅读器宏,它们在“读取时”(或者如果您喜欢,“读取之前”)转换输入。我没有做过太多大规模的阅读器宏操作,但我编写了一些代码,允许将elisp源代码(大部分)读入Common Lisp中,在两者之间有相当多的语法糖微妙差异。


0

我相信这种宏被称为代码遍历宏。我自己没有实现过代码遍历器,所以对其限制不熟悉。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接