Common Lisp 中宏扩展顺序的规则
Rules governing order of macro expansion in Common Lisp
defmacro
记录在 http://clhs.lisp.se/Body/m_defmac.htm 但文档并不完全清楚事情发生的确切时间。通过使用 Clisp 进行实验,我发现了以下内容(假设所有宏和函数都在顶层定义):
直接顶层代码只能调用前面定义好的宏和函数
宏或函数中的代码,或由宏生成的代码,可以调用它喜欢的任何函数,包括稍后定义的函数(正如支持相互递归的需要所预期的)。
宏中的代码只能调用早于第一个宏的调用站点定义的宏。
宏生成的代码可以调用后面定义的宏
Clisp 是否只是遵循规范,或者在这方面实现之间是否存在任何差异?
是否在任何地方记录了确切的预期规则集及其背后的基本原理?
您问的是宏扩展 - 但我想先说明函数是如何处理的。
注意调用和定义实际发生的时间。在您的第二点中,您说函数中的代码可以调用稍后定义的函数。这不是严格意义上的。
在 C++ 等语言中,您声明和定义函数,然后编译您的应用程序。忽略内联、模板、lambda 和其他魔术...,在编译函数时,该函数使用的所有其他函数的声明都需要存在——并且在 link 时,编译的定义需要存在——所有在程序开始之前 运行ning。一旦程序启动 运行ning,所有函数都已经准备就绪,可以调用了。
现在在 Lisp 中,情况有所不同。现在忽略编译——让我们考虑一个解释环境。如果你 运行:
;; time 1
(defun a () (b))
;; time 2
(defun b () 123)
;; time 3
(a)
在时间 1 你的程序没有函数。
第一个defun
然后创建一个函数(lambda () (b))
,并将其与符号a
相关联。此函数包含对符号 b
的引用,但 此时它并未调用 b
。 a
只会在 a
本身被调用时调用 b
。
因此,在时间 2 你的程序有一个函数,与符号 a
相关联,但尚未执行。
现在第二个 defun
创建一个函数 (lambda () 123)
,并将其与符号 b
相关联。
在时间 3 你的程序有两个函数,与符号 a
和 b
相关联,但都没有被调用。
现在你打电话给a
。在执行过程中,它会寻找与符号b
关联的函数,发现此时已经存在这样的函数,并调用它。 b
执行并且 returns 123.
让我们添加更多代码:
;; time 4
(defun b () 456)
;; time 5
(a)
在时间 4 之后,一个新的 defun
创建一个返回 456 的函数,并将它与符号 b
相关联。这取代了 b
持有返回 123 的函数的引用,然后将对其进行垃圾收集(或您执行的任何清除垃圾的操作)。
调用a
(或者更准确地说,符号a
的函数属性引用的lambda),现在将导致调用returns 456的函数。
如果我们最初写成:
;; time 1
(defun a () (b))
;; time 2
(a)
;; time 3
(defun b () 123)
...这不会有效,因为在我们调用a
的时间2之后,它找不到与符号[=相关联的函数16=] 所以它会失败。
现在 - compile
、eval-when
、优化和其他魔术可以做各种与我上面描述的不同的时髦事情,但请确保您首先掌握这些基础知识在担心更高级的东西之前。
- 函数仅在调用
defun
时创建。 (解释器不会“在文件中向前看”。)
- 符号的属性之一是对函数的引用。 (函数本身实际上没有名称。)
- 多个符号可以引用同一个函数。 (
(setf (symbol-function 'd) (symbol-function 'b))
)
- 定义调用函数
b
的函数 a
(通俗地说),只要符号 b
有关联函数 就可以了a
被称为。 (defun
宁a
时不需要。)
- 一个符号在不同的时间可以代表不同的功能。这会影响“调用”该符号的任何函数。
宏的规则 不同(它们的扩展在“读取”时间后是静态的),但许多原则保持不变(Lisp 不会“向前看”文件”找到它们)。请理解,Lisp 程序比您可能习惯的大多数(较少 ;-) )语言更具动态性和“运行-时间”。了解在执行 Lisp 程序期间发生什么何时,控制宏扩展的规则将开始变得有意义。
defmacro
记录在 http://clhs.lisp.se/Body/m_defmac.htm 但文档并不完全清楚事情发生的确切时间。通过使用 Clisp 进行实验,我发现了以下内容(假设所有宏和函数都在顶层定义):
直接顶层代码只能调用前面定义好的宏和函数
宏或函数中的代码,或由宏生成的代码,可以调用它喜欢的任何函数,包括稍后定义的函数(正如支持相互递归的需要所预期的)。
宏中的代码只能调用早于第一个宏的调用站点定义的宏。
宏生成的代码可以调用后面定义的宏
Clisp 是否只是遵循规范,或者在这方面实现之间是否存在任何差异?
是否在任何地方记录了确切的预期规则集及其背后的基本原理?
您问的是宏扩展 - 但我想先说明函数是如何处理的。
注意调用和定义实际发生的时间。在您的第二点中,您说函数中的代码可以调用稍后定义的函数。这不是严格意义上的。
在 C++ 等语言中,您声明和定义函数,然后编译您的应用程序。忽略内联、模板、lambda 和其他魔术...,在编译函数时,该函数使用的所有其他函数的声明都需要存在——并且在 link 时,编译的定义需要存在——所有在程序开始之前 运行ning。一旦程序启动 运行ning,所有函数都已经准备就绪,可以调用了。
现在在 Lisp 中,情况有所不同。现在忽略编译——让我们考虑一个解释环境。如果你 运行:
;; time 1
(defun a () (b))
;; time 2
(defun b () 123)
;; time 3
(a)
在时间 1 你的程序没有函数。
第一个defun
然后创建一个函数(lambda () (b))
,并将其与符号a
相关联。此函数包含对符号 b
的引用,但 此时它并未调用 b
。 a
只会在 a
本身被调用时调用 b
。
因此,在时间 2 你的程序有一个函数,与符号 a
相关联,但尚未执行。
现在第二个 defun
创建一个函数 (lambda () 123)
,并将其与符号 b
相关联。
在时间 3 你的程序有两个函数,与符号 a
和 b
相关联,但都没有被调用。
现在你打电话给a
。在执行过程中,它会寻找与符号b
关联的函数,发现此时已经存在这样的函数,并调用它。 b
执行并且 returns 123.
让我们添加更多代码:
;; time 4
(defun b () 456)
;; time 5
(a)
在时间 4 之后,一个新的 defun
创建一个返回 456 的函数,并将它与符号 b
相关联。这取代了 b
持有返回 123 的函数的引用,然后将对其进行垃圾收集(或您执行的任何清除垃圾的操作)。
调用a
(或者更准确地说,符号a
的函数属性引用的lambda),现在将导致调用returns 456的函数。
如果我们最初写成:
;; time 1
(defun a () (b))
;; time 2
(a)
;; time 3
(defun b () 123)
...这不会有效,因为在我们调用a
的时间2之后,它找不到与符号[=相关联的函数16=] 所以它会失败。
现在 - compile
、eval-when
、优化和其他魔术可以做各种与我上面描述的不同的时髦事情,但请确保您首先掌握这些基础知识在担心更高级的东西之前。
- 函数仅在调用
defun
时创建。 (解释器不会“在文件中向前看”。) - 符号的属性之一是对函数的引用。 (函数本身实际上没有名称。)
- 多个符号可以引用同一个函数。 (
(setf (symbol-function 'd) (symbol-function 'b))
) - 定义调用函数
b
的函数a
(通俗地说),只要符号b
有关联函数 就可以了a
被称为。 (defun
宁a
时不需要。) - 一个符号在不同的时间可以代表不同的功能。这会影响“调用”该符号的任何函数。
宏的规则 不同(它们的扩展在“读取”时间后是静态的),但许多原则保持不变(Lisp 不会“向前看”文件”找到它们)。请理解,Lisp 程序比您可能习惯的大多数(较少 ;-) )语言更具动态性和“运行-时间”。了解在执行 Lisp 程序期间发生什么何时,控制宏扩展的规则将开始变得有意义。