您需要了解有关 Racket 的几件事:
在 Racket 中,每个文件(以 开头#lang
)都是一个模块,这与许多没有模块的(传统,r5rs)方案不同。
模块的作用域规则类似于函数的规则,因此在某种意义上,这些定义类似于函数中的定义。
Racket 从左到右评估定义。在方案行话中,您说 Racket 的定义具有letrec*
语义;这与一些使用letrec
语义的方案不同,其中相互递归的定义永远不会起作用。
所以底线是定义都是在模块的环境中创建的(类似于在函数中,对于函数局部定义),然后它们从左到右初始化。因此,反向引用总是有效的,所以你总是可以做类似的事情
(define a 1)
(define b (add1 a))
它们是在单个范围内创建的——因此理论上,前向定义在它们在范围内的意义上是有效的。但实际上使用前向引用的值是行不通的,因为在#<undefined>
评估实际值之前你会得到一个特殊的值。要看到这一点,请尝试运行以下代码:
#lang racket
(define (foo)
(define a a)
a)
(foo)
模块的顶层受到进一步限制,因此此类引用实际上是错误,您可以通过以下方式看到:
#lang racket
(define a a)
考虑到所有这些,对于函数内部的引用来说,事情会更加宽容。问题是函数的主体在函数被调用之前不会执行——所以如果在函数内部发生前向引用,那么它是有效的(= 不会得到错误或#<undefined>
)如果函数在所有绑定已初始化。这适用于普通函数定义
(define foo (lambda () a))
使用常用语法糖的定义
(define (foo) a)
甚至最终扩展为功能的其他形式
(define foo (delay a))
有了所有这些,您将不会通过相同的规则得到任何错误——当函数体的所有使用都发生在定义初始化之后。
然而,一个重要的注意事项是,您不应该将这种初始化与赋值混淆。这意味着像
(define x (+ x 1))
不等同于x = x+1
主流语言。它们更像是某种var x = x+1
语言中的一些,会因“引用未初始化的变量”错误而失败。这是因为在当前范围内define
创建了一个新绑定,它不会“修改”现有的绑定。