您问的是宏扩展 - 但我想先澄清一下函数是如何处理的。
注意调用和定义实际发生的时间。在您的第二点中,您说函数中的代码可以调用稍后定义的函数。这并不完全正确。
在 C++ 等语言中,您需要声明和定义函数,然后编译您的应用程序。忽略内联、模板、lambdas 和其他魔法......,在编译函数时,该函数使用的所有其他函数的声明都需要存在 - 并且在链接时,编译的定义需要存在 - 都在程序之前开始运行。一旦程序开始运行,所有函数都已经准备就绪,可以调用了。
现在在 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 执行并返回 123。
让我们添加更多代码:
;; time 4
(defun b () 456)
;; time 5
(a)
在时间 4 之后,新的 defun 创建一个返回 456 的函数,并将其与符号 b 相关联。这将替换引用 b 对返回 123 的函数的引用,该函数随后将被垃圾收集(或您实现的任何清除垃圾的操作)。
调用a(或更准确地说,符号a 的函数属性引用的lambda)现在将导致调用返回456 的函数。
如果相反,我们最初是这样写的:
;; time 1
(defun a () (b))
;; time 2
(a)
;; time 3
(defun b () 123)
...这不会起作用,因为在我们调用a 的时间2 之后,它找不到与符号b 关联的函数,因此它将失败。
现在 - compile、eval-when、优化和其他魔法可以做与我上面描述的不同的各种时髦的事情,但请确保您首先掌握这些基础知识,然后再担心更高级的东西。
- 函数仅在调用
defun 时创建。 (解释器不会“在文件中向前看”。)
- 符号的属性之一是对函数的引用。 (函数本身实际上没有名称。)
- 多个符号可以引用同一个函数。 (
(setf (symbol-function 'd) (symbol-function 'b)))
- 定义一个调用函数
b(通俗地说)的函数a 是可以的,只要符号b 在调用a 时有一个关联函数。 (defunninga时不需要。)
- 一个符号可以在不同的时间引用不同的功能。这会影响任何“调用”该符号的函数。
宏的规则是不同的(它们的扩展在“读取”时间之后是静态的),但许多原则保持不变(Lisp 不会“在文件中向前看”来查找他们)。了解 Lisp 程序比您可能习惯的大多数(较小的 ;-) )语言更具动态性和“运行时”。了解在 Lisp 程序执行期间发生了什么何时,管理宏扩展的规则将开始有意义。