我看到了 How does Lisp let you redefine the language itself? 的一个答案 Stack Overflow问题(由Noah Lavine回答):
宏并不是对语言的完全重新定义,至少据我所知(我实际上是一个Schemer;我可能是错的),因为有一个限制。宏只能占用代码的单个子树,并生成一个子树来替换它。因此,你不能编写整个程序转换宏,就像那样酷。
阅读本文之后,我很好奇Lisp或Scheme(或其他语言)中是否存在“整个程序转换宏”。
如果没有那么为什么?
更新
一种用例 e.g。
在stumpwm代码中 这里有一些函数都在不同的lisp源文件中 使用 primitives.lisp 中定义的动态/全局defvar变量 * screen-list * ,但在 screen.lisp , user.lisp , window.lisp 。 (这里每个文件都有与一个方面或对象相关的函数,类,变量)
现在我想在闭包中定义这些函数 通过let表单可以获得 * screen-list * 变量,它不应该是 动态/全局变量,但不将所有功能都移动到 一个地方(因为我不希望这些功能失去他们的位置 相关文件) 因此,只有这些函数才能访问此变量。
例如同样适用于标签和flet,以便它更有可能 我们可以使它像只需要变量,功能将可用, 那些需要它的人。
请注意一种方法 实现并使用一些宏defun_with_context进行defun,其中第一个参数是 上下文中let,flet变量definend。 但除此之外,它可以通过读者 - 宏来实现 Vatine和Gareth Rees回答道。
答案 0 :(得分:5)
你引用Noah Lavine的话说:
宏只能占用代码的单个子树,并生成一个子树来替换它
普通宏就是这种情况,但是读取器宏可以访问输入流,可以随心所欲地做任何事情。
请参阅Hyperspec section 2.2和set-macro-character
功能。
答案 1 :(得分:5)
在Racket中,您可以实现整个程序转换宏。请参阅有关defining new languages的文档中的部分。在Racket中有很多这样的例子,例如懒惰语言和Typed Racket。
答案 2 :(得分:2)
离开我的头顶,有几种方法:
首先,你可以。 Norvig points out那个:
我们可以将编译器编写为一组宏。
如果您愿意,可以转换整个程序。我只是很少看到它,因为通常“你想对程序的每个部分做的事情”和“你需要宏/ AST类型转换的事情”之间的交集是一个非常小的集合。一个例子是Parenscript,它将你的Lisp代码(“CL的扩展子集”)转换为Javascript。我用它将Lisp代码的整个文件编译成Javascript,直接提供给Web客户端。这不是我最喜欢的环境,但它做的是广告宣传。
另一个相关的功能是“建议”,Yegge describes as:
伟大的系统也有建议。这个功能没有普遍接受的名称。有时它被称为钩子,过滤器或面向方面的编程。据我所知,Lisp首先使用它,它在Lisp中被称为建议。建议是一个迷你框架,提供钩子之前,周围和之后,通过它可以编程修改系统中某些动作或函数调用的行为。
另一个是special variables。通常,宏(和其他构造)适用于词法范围。通过声明变量是特殊的,你告诉它应用于动态范围(我认为它是“时间范围”)。我想不出任何其他语言可以让你(程序员)在这两者之间做出选择。而且,除了编译器的情况,这两个真正跨越了我作为程序员感兴趣的空间。
答案 3 :(得分:1)
一种典型的方法是编写自己的模块系统。如果您只想访问所有代码,您可以使用自己的模块注释来获得某种预处理器或阅读器扩展包装源文件。如果您随后编写自己的require
或import
表单,则最终可以查看范围内的所有代码。
首先,您可以编写自己的module
表单,该表单允许您定义几个函数,然后在发出优化代码之前以一种巧妙的方式编译。
答案 4 :(得分:0)
总是可以选择使用编译器宏(它们可以根据标准的条件进行全功能转换,但不应该更改返回的值,因为这会让人感到困惑)。
有读取器宏,它们在“读取时”(或“在读取之前”,如果您愿意)转换输入。我没有做太多的大规模读者宏观黑客攻击,但是我已经编写了一些代码来允许elisp sourec(大部分)在Common Lisp中读取,两者之间的语法糖差别很小。
答案 5 :(得分:0)
我相信那些宏被称为代码行走宏。我自己没有实现代码漫游器,所以我不熟悉限制。
答案 6 :(得分:0)
在Common LISP中,至少,您可以在PROGN中包装顶级表单,并且它们仍然保留其作为顶级表单的状态(请参阅CLTL2, section 5.3)。因此,生成单个子树的宏的限制不是很大限制,因为它可以在PROGN中包装任意数量的结果子树。这使整个程序宏成为可能。
E.g。
(my-whole-program-macro ...)
= expands to =>
(progn
(load-system ...)
(defvar ...)
(defconstant ...)
(defmacro ...)
(defclass ...)
(defstruct ...)
(defun ...)
(defun ...)
...
)