函数中的变量

2024-04-01

我看到了下面的代码...第一次调用(next-num)回报1,第二个返回2.

(define next-num
  (let ((num 0))
    (lambda () (set! num (+ num 1)) num)))

(next-num)   ; 1
(next-num)   ; 2

我无法理解的是...num是由创建的let inside next-num,它是一种局部变量...scheme 如何知道每次next-num被称为,值num未被擦除let ((num 0));方案如何知道它总是相同的num我们随时修改next-num叫做?

看起来num既是局部变量又是静态变量...我们如何定义局部变量,但不是静态变量?


这是“词汇闭合”,你是对的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对于任何想要了解范围界定等问题的人来说,关于计划实施的文章都是一本很好的拓展思维的读物。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数中的变量 的相关文章

随机推荐