这是“词汇闭合”,你是对的num
,“封闭变量”类似于静态变量,例如在 C 语言中:它仅对 C 语言中的代码可见。let
形式(它的“词法范围”),但它在整个程序运行中持续存在,而不是在每次调用函数时重新初始化。
我认为你感到困惑的部分是:“num
由let创建next-num
,它是一种局部变量”。这是不正确的,因为let
块不属于next-num
函数:它实际上是一个表达式,它创建并返回函数,然后将其绑定到next-num
。 (这与 C 非常不同,例如,C 中的函数只能在编译时创建并通过在顶层定义它们来创建。在Scheme 中,函数是整数或列表之类的值,任何表达式都可以返回它们)。
这是(几乎)相同内容的另一种写法,这使得更清楚的是define
只是关联next-num
函数返回表达式的值:
(define next-num #f) ; dummy value
(let ((num 0))
(set! next-num
(lambda () (set! num (+ num 1)) num)))
重要的是要注意之间的区别
(define (some-var args ...) expression expression ...)
这使得some-var
一个执行所有的函数expressions
当被呼叫时,并且
(define some-var expression)
这结合了some-var
的值expression
,当场评估。严格来说,前一个版本是不必要的,因为它相当于
(define some-var
(lambda (args ...) expression expression ...))
您的代码几乎与此相同,添加了词法作用域变量,num
, 周围lambda
form.
最后,这是封闭变量和静态变量之间的一个关键区别,这使得闭包更加强大。如果您写的是以下内容:
(define make-next-num
(lambda (num)
(lambda () (set! num (+ num 1)) num)))
然后每次调用make-next-num
将创建一个带有新的、不同的匿名函数num
变量,该函数私有:
(define f (make-next-num 7))
(define g (make-next-num 2))
(f) ; => 8
(g) ; => 3
(f) ; => 9
这是一个非常酷且强大的技巧,它解释了具有词法闭包的语言的许多功能。
编辑添加:你问计划如何“知道”哪个num
修改时next-num
叫做。总的来说,如果不具体实施的话,这实际上非常简单。 Scheme 中的每个表达式都在变量绑定的环境(查找表)上下文中进行计算,变量绑定是名称与可以保存值的位置的关联。每次评价一个let
表单或函数调用通过使用新绑定扩展当前环境来创建新环境。安排有lambda
形式表现为闭包,实现将它们表示为由函数本身加上定义它的环境组成的结构。然后通过扩展定义该函数的绑定环境来评估对该函数的调用 -not它被调用的环境。
较旧的 Lisp(包括直到最近的 Emacs Lisp)有lambda
,但不是词法范围,因此尽管您可以创建匿名函数,但对它们的调用将在调用环境而不是定义环境中进行评估,因此不存在闭包。我相信Scheme 是第一种做到这一点的语言。萨斯曼和斯蒂尔的原著拉姆达论文 http://library.readscheme.org/page1.html对于任何想要了解范围界定等问题的人来说,关于计划实施的文章都是一本很好的拓展思维的读物。