Racket 和 Common Lisp 中顶级函数定义顺序的规则

Rob*_*ert 1 lisp common-lisp racket

我发现顶级声明顺序似乎并不重要。是否有关于该主题的任何文档?我不太明白。

显示函数可以在未定义的情况下被调用的示例

#lang racket

(define (function-defined-early)
  (function-defined-later))

(define (function-defined-later)
  1)

(function-defined-early)
> 1
Run Code Online (Sandbox Code Playgroud)
;; Common Lisp

(defun function-defined-early ()
  (function-defined-later))

(defun function-defined-later ()
  1)

(print (function-defined-early))
> 1
Run Code Online (Sandbox Code Playgroud)

Rai*_*wig 5

对于 Common Lisp,它有点复杂,因为实现可以使用解释代码、编译代码和高度优化的编译代码。

简单编译代码中的函数调用

例如,SBCL 默认编译所有代码。即使是通过 read-eval-print-loop 输入的代码:

* (defun foo (a) (bar (1+ a)))
; in: DEFUN FOO
;     (BAR (1+ A))
;
; caught STYLE-WARNING:
;   undefined function: COMMON-LISP-USER::BAR
;
; compilation unit finished
;   Undefined function:
;     BAR
;   caught 1 STYLE-WARNING condition
FOO
Run Code Online (Sandbox Code Playgroud)

由于函数被立即编译,编译器会发现有一个未定义的函数。但这只是警告而不是错误。生成的代码将调用 function bar,即使它是稍后定义的。

符号具有函数值

在 Common Lisp 中,全局函数的函数对象被注册为符号。

* (fboundp 'foo)
T
* (fboundp 'bar)
NIL
Run Code Online (Sandbox Code Playgroud)

bar没有函数定义。如果我们稍后为 定义一个函数bar,那么我们之前定义的函数的代码foo将调用这个新函数。

它是如何工作的?中的代码在foo运行时进行查找以获取符号的函数值bar并调用该函数。

因此,我们也可以重新定义barfoo调用新函数。

后期绑定

对函数进行运行时查找的概念通常称为后期绑定。这是 1960 年代为 Lisp 描述的。

因此调用全局函数

(bar 1 a)
Run Code Online (Sandbox Code Playgroud)

在概念上基本上与

(if (fbound 'bar)
    (funcall (symbol-function 'bar) 1 a)
    (error "undefined function BAR"))
Run Code Online (Sandbox Code Playgroud)

请记住,这是一个简单的模型,实际上 Common Lisp文件编译器可能会使用更积极的优化(如内联),其中没有运行时查找。

函数形式的评估

Common Lisp 标准在Conses as Forms 中说:

如果运算符既不是特殊运算符也不是宏名称,则假定它是函数名称(即使没有定义这样的函数)。