在Racket的 Scheme中,晚绑定无法在let环境中找到
我开始用Racket学习Scheme编程语言。我有一个函数,其中调用了另一个函数,然而这个内部函数在外部函数定义之后才被定义。如果我在定义外部函数的全局环境中定义内部函数,一切正常,但在let环境中,内部函数似乎尚未绑定,我会得到未绑定标识符的错误。下面是我的代码:
这段代码失败:
#lang racket
(define (outer x) (lambda (y) (later-func x y)))
(let ([later-func (lambda (x y) (+ x y))])
((outer 8) 9))
但这个版本走运地返回了17:
#lang racket
(define (outer x) (lambda (y) (later-func x y)))
(define (later-func x y) (+ x y))
((outer 8) 9)
更新
在下面Scott Hunter的建议之后,我仍然很难理解到底重要的是最近的外层环境,还是这在我写的另一个案例中的应用。这里名称 var 最先在全局绑定,然后又在函数 f 内部绑定。对内部函数 g 的调用应该反映最近外层作用域中的 var,根据Scott Hunter的定义,应该是9:
(define var 6)
(define (f)
(define g (lambda () (display var) (newline)))
(g)
(define var 9)
(display "---") (newline)
(g)
)
(f)
现在这段代码会输出:
6
---
6
只有当我把对 g 的第一次调用注释掉时才会是9:
(define var 6)
(define (f)
(define g (lambda () (display var) (newline)))
;(g)
(define var 9)
(display "---") (newline)
(g)
)
(f)
这会输出:
---
9
为什么第一处对 g 的调用若不被注释掉,反映出的最近对 var 的绑定就不是我所理解的应该是9 吗?
注:这段代码是按照 R5RS 编写的,这是不是也许不是实际更新后的Scheme规范?
在 #lang racket 这段代码会给出如下的警告:
var: undefined;
cannot use before initialization
指向 var 在 g 的主体中的位置。
再举一个例子
在函数体内对全局名称进行一个简单的重新定义。我预计在函数体内对重新定义的名称的引用来自于函数环境中的名称。但Racket-scheme在这里给出错误:
#lang racket
; A global number
(define number 6)
(define (g)
(displayln number) ; ERROR: should this not actually be the number in current env?!
(define number 9) ; Re-define number in function-env
number)
(g)
导致
number: undefined;
cannot use before initialization
指向函数体中第 (displayln number) 行的 ERROR。
解决方案
Scheme是一种静态作用域的语言,这意味着变量的绑定取决于源代码中最近的外层 define 或 let。
在你能够工作的版本中,later-func 定义在最外层作用域,因此可以在 outer 内看到它。但在不起作用的版本中,你必须在 let 的内部去查找 later-func 的定义,来自于 outer 的定义内部;无论你是在该 let 内对 outer 进行调用,这一点都无关紧要。