跳转到内容

Scheme 编程/局部作用域

来自维基教科书,自由的教科书,为一个自由的世界
Scheme 编程
 ← 可变性 局部作用域 面向对象 → 

本节定义了局部作用域和一般的变量作用域。

变量作用域概念

[编辑 | 编辑源代码]

Scheme 使用词法作用域,这意味着变量作用域可以直接从程序文本中看到,并且变量作用域在编译时定义。

Scheme 是块结构化的。这里块是指一段具有开始和结束点的代码行。块定义一个作用域,作用域是变量定义(或绑定)的地方。块可以嵌套。存在外部块和内部块。

顶级作用域是一个特殊的作用域,因为根据定义,它没有外部作用域。顶级作用域也称为全局作用域。任何其他作用域都是嵌套的,并且它们都受外部作用域或作用域的影响。局部作用域与当前作用域相同。


顶级作用域

[编辑 | 编辑源代码]

顶级作用域是程序作用域中最外层的范围(块)。

我们在这里定义一个在顶级作用域中的变量并评估它的值

> (define x 1)
> x
1


正式地说,我们可以说变量x绑定到值 1。一般来说,顶级作用域包含全局绑定,这些绑定可以是过程或数据值。


局部作用域

[编辑 | 编辑源代码]

可以使用let表达式创建新的作用域(即当前作用域的内部作用域)

> (let ((y 2))
>   y)
2

let表达式的值是let主体内的最后一个评估的表达式。在这种情况下,我们得到y的值,它为 2。

变量y只存在于内部作用域中

> y
;; Error: unbound variable: y

但是,来自顶级作用域的变量对所有内部作用域可见

> (define x 1)
> (let ((y 2))
>   (+ x y))
3

可以在内部作用域中使用相同的变量名。即使内部作用域中的变量与外部作用域中的变量具有相同的名称,它们也是不同的绑定,并且它们存储在不同的位置。Scheme 从找到变量标识符的最内部作用域使用变量绑定。

因此,我们可以遮蔽外部作用域中的变量

> (define x 1)
> (let ((x 7))
>   x)
7
> x
1

内部x只存在于let表达式中,但外部xlet表达式之前和之后存在。

块的嵌套深度没有限制

> (define x 1)
> (let ((x 7))
>   (let ((x 13))
>     x))
13
> x
1

请注意,上面的代码只演示了作用域的嵌套,一般来说没有多大意义。


到目前为止,我们已经引用了来自正常表达式的变量,这些表达式产生数据值。但是,我们也可以从过程引用外部作用域中的变量。词法作用域规则对过程的应用方式与对其他表达式的应用方式相同。实际上,let可以表示为lambda的语法扩展,因此实际上我们一直都在引用过程中的变量。

当过程引用一个不在过程本身(即在外部作用域中绑定)中绑定的变量时,就会创建一个闭包。外部变量成为闭包的一部分。这种连接非常强大,即使变量的作用域已经过期,它仍然存在于闭包中。从某种意义上说,闭包打破了变量的正常块作用域。当闭包是从内部作用域传递到外部作用域的值时,就会发生这种情况。

这里有一个非常简单的闭包示例

> (define x 0)
> (define inc-x (lambda ()
                  (set! x (+ x 1))
                  x))
> (inc-x)
1
> (inc-x)
2

inc-x每次评估时都会将x增加 1。该过程不接受任何参数。inc-x引用外部变量x。此外,还值得注意的是它也引用了++实际上是 Scheme 中的一个变量,它恰好绑定到一个将数字加在一起的过程。

当 lambda 引用无法在外部访问的变量时,闭包更有用。你可以使用 let 在 lambda 上做到这一点

> (define inc (let ((x 0))
                (lambda ()
                  (set! x (+ x 1))
                  x)))
> (inc)
1
> (inc)
2
> x
;; x is unbound variable

这里与前面的例子相同,lambda 关闭了 x 变量,但是这次它在 let 内部,因此它无法在外部访问。

闭包可以以多种方式使用。高阶函数将其他函数作为参数并应用它们,参数可以是闭包。闭包也可以用来模拟对象。它们在外部变量中维护状态,并在每次应用时更新状态。

这是一个示例,其中闭包与高阶函数map一起使用

> (define x 1)
> (map (lambda (num)
         (+ num x))
       '(10 11 12))
(11 12 13)

map接受两个参数:一个过程和一个列表。该过程应用于列表中的每个元素,并根据过程应用的结果创建一个新列表。

此示例中涉及两个作用域:顶级作用域和lambda创建的内部作用域。num存在于lambda的局部作用域中,但x不存在。因此,xlambda创建的闭包的一部分。map正常表达式的一部分,并且它不会启动新的作用域。

华夏公益教科书