为什么会这样:
例如,
a)以下代码段错误:
; Must define function `f` before variable `a`.
#lang racket
(define a (f))
(define (f) 10)
b)以下代码段是正确的:
; Function `g` could be defined after function `f`.
#lang racket
(define (f) (g)) ; `g` is not defined yet
(define (g) 10)
c)正确 :
; Variable `a` could be defined after function `f`
#lang racket
(define (f) a) ; `a` is not defined yet
(define a 10)
答案 0 :(得分:3)
您需要了解有关Racket的一些事项:
在Racket中,每个文件(以#lang
开头)都是一个模块,不像许多(传统的,r5rs)方案没有模块。
模块的范围规则类似于函数的规则,因此从某种意义上说,这些定义类似于函数中的定义。
球拍从左到右评估定义。在方案术语中,你说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
在当前范围内创建新绑定,它不会“修改”现有绑定。
答案 1 :(得分:2)
以下是近似的一般方案描述,类比。
定义一个功能
(define (f) (g))
或多或少像
f := (lambda () (g))
因此评估lambda表达式,并将得到的函数对象(通常是闭包)存储在正在定义的新变量f
中。当调用函数g
时,必须定义函数f
同样,(define (h) a)
与h := (lambda () a)
类似,因此只有在调用函数h
时,才会检查对变量a
的引用,以找到其值。
但是
(define a (f))
就像
a := (f)
即。必须在没有参数的情况下调用函数f
,并且定义存储在新变量a
中的调用的结果。因此,必须在此时定义函数。
文件中的每个定义都是一个接一个地按顺序执行。允许每个定义引用文件中定义的任何变量,无论是在它上面还是下面(它们都被认为属于相同的范围),但是允许仅使用其上面定义的变量的值。
(这里有一个含糊不清的地方:假设你使用的是内置的函数,比如说(define a (+ 1 2))
,但是后来也在文件中定义它,比如说(define + -)
。是定义还是重新定义?在第一种情况下,是Racket的选择,禁止在定义之前使用。在第二种情况下,&#34 ; global&#34; value用于计算a
的值,然后重新定义函数。一些方案可能会走这条路。感谢Eli Barzilay向我展示这一点,以及Chris Jester-Young帮忙)。