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 的引用,但 此时它并未调用 ba 只会在 a 本身被调用时调用 b

因此,在时间 2 你的程序有一个函数,与符号 a 相关联,但尚未执行。

现在第二个 defun 创建一个函数 (lambda () 123),并将其与符号 b 相关联。

在时间 3 你的程序有两个函数,与符号 ab 相关联,但都没有被调用。

现在你打电话给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=] 所以它会失败。

现在 - compileeval-when、优化和其他魔术可以做各种与我上面描述的不同的时髦事情,但请确保您首先掌握这些基础知识在担心更高级的东西之前。

  1. 函数仅在调用 defun 时创建。 (解释器不会“在文件中向前看”。)
  2. 符号的属性之一是对函数的引用。 (函数本身实际上没有名称。)
  3. 多个符号可以引用同一个函数。 ((setf (symbol-function 'd) (symbol-function 'b)))
  4. 定义调用函数 b 的函数 a(通俗地说),只要符号 b 有关联函数 就可以了a被称为。 (defuna时不需要。)
  5. 一个符号在不同的时间可以代表不同的功能。这会影响“调用”该符号的任何函数。

宏的规则 不同(它们的扩展在“读取”时间后是静态的),但许多原则保持不变(Lisp 不会“向前看”文件”找到它们)。请理解,Lisp 程序比您可能习惯的大多数(较少 ;-) )语言更具动态性和“运行-时间”。了解在执行 Lisp 程序期间发生什么何时,控制宏扩展的规则将开始变得有意义。