跳轉到內容

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正常表示式的一部分,並且它不會啟動新的作用域。

華夏公益教科書