這是「詞法閉包」,你說得對,num
的「封閉變量」類似於一個靜態變量,在C例如:這只是可見的代碼let
表單中(它的「詞彙範圍「),但它在整個程序運行過程中保持不變,而不是在每次調用函數時重新初始化。
我覺得你對此感到困惑的部分是這樣的:「num
是通過在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
形式。
最後,這是閉合變量和靜態變量之間的一個關鍵區別,它使閉包變得更加強大。反而如果你寫了以下內容:
(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
這是一個非常酷且強大的技巧,它解釋了詞彙封閉式語言的很多功能。
修改爲添加:您可以要求Scheme如何「知道」調用next-num
時要修改哪個num
。總的來說,如果不是在實施中,這實際上很簡單。 Scheme中的每個表達式都是在變量綁定環境(查找表)的上下文中進行評估的,這些變量綁定是名稱與可以保存值的地方的關聯。每個對let
表單或函數調用的評估都會通過使用新綁定擴展當前環境來創建新環境。爲了安排lambda
表單作爲閉包,該實現將它們表示爲由函數本身加上其定義的環境組成的結構。然後通過擴展定義函數的綁定環境來評估對該函數的調用 - 而不是它調用的環境。
舊的Lisp(包括的Emacs Lisp直到最近)有lambda
,而不是詞彙範圍,所以雖然你可以創建一個匿名函數,調用它們將在調用環境,而不是定義環境進行評估,因此沒有關閉。我相信Scheme是第一個正確的語言。 Sussman和Steele最初的Lambda Papers關於計劃的實施對於任何想要了解範圍界定以及其他許多事情的人來說都是非常有意思的擴展閱讀。