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表示式中,但外部x在let表示式之前和之後存在。
塊的巢狀深度沒有限制
> (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不存在。因此,x是lambda建立的閉包的一部分。map是正常表示式的一部分,並且它不會啟動新的作用域。