Closure

2018-02-24 15:53 更新

Closure

目前為止關(guān)于函數(shù)式編程各種功能的討論都只局限在“純”函數(shù)式語(yǔ)言范圍內(nèi):這些語(yǔ)言都是lambda演算的實(shí)現(xiàn)并且都沒有那些和阿隆佐形式系統(tǒng)相沖突的特性。然而,很多函數(shù)式語(yǔ)言的特性哪怕是在lambda演算框架之外都是很有用的。確實(shí),如果一個(gè)公理系統(tǒng)的實(shí)現(xiàn)可以用數(shù)學(xué)思維來(lái)看待程序,那么這個(gè)實(shí)現(xiàn)還是很有用的,但這樣的實(shí)現(xiàn)卻不一定可以付諸實(shí)踐。很多現(xiàn)實(shí)中的語(yǔ)言都選擇吸收函數(shù)式編程的一些元素,卻又不完全受限于函數(shù)式教條的束縛。很多這樣的語(yǔ)言(比如Common Lisp)都不要求所有的變量必須為final,可以修改他們的值。也不要求函數(shù)只能依賴于它們的參數(shù),而是可以讀寫函數(shù)外部的狀態(tài)。同時(shí)這些語(yǔ)言又包含了FP的特性,如高階函數(shù)。與在lambda演算限制下將函數(shù)作為參數(shù)傳遞不同,在指令式語(yǔ)言中要做到同樣的事情需要支持一個(gè)有趣的特性,人們常把它稱為lexical closure。還是來(lái)看看例子。要注意的是,這個(gè)例子中變量不是final,而且函數(shù)也可以讀寫其外部的變量:

 Function makePowerFn(int power) {
   int powerFn(int base) {
       return pow(base, power);
   }

   return powerFn;
}

Function square = makePowerFn(2);
square(3); // returns 9

makePowerFn函數(shù)返回另一個(gè)函數(shù),這個(gè)新的函數(shù)需要一個(gè)整數(shù)參數(shù)然后返回它的平方值。執(zhí)行square(3)的時(shí)候具體發(fā)生了什么事呢?變量power并不在powerFn的域內(nèi),因?yàn)閙akePowerFn早就運(yùn)行結(jié)束返回了,所以它的棧也已經(jīng)不存在了。那么square又是怎么正常工作的呢?這個(gè)時(shí)候需要語(yǔ)言通過(guò)某種方式支持繼續(xù)存儲(chǔ)power的值,以便square后面繼續(xù)使用。那么如果再定義一個(gè)函數(shù),cube,用來(lái)計(jì)算立方,又應(yīng)該怎么做呢?那么運(yùn)行中的程序就必須存儲(chǔ)兩份power的值,提供給makePowerFn生成的兩個(gè)函數(shù)分別使用。這種保存變量值的方法就叫做closure。closure不僅僅保存宿主函數(shù)的參數(shù)值,還可以用在下例的用法中:

Function makeIncrementer() {
   int n = 0;

   int increment() {
       return ++n;
   }
}

Function inc1 = makeIncrementer();
Function inc2 = makeIncrementer();

inc1(); // returns 1;
inc1(); // returns 2;
inc1(); // returns 3;
inc2(); // returns 1;
inc2(); // returns 2;
inc2(); // returns 3;

運(yùn)行中的程序負(fù)責(zé)存儲(chǔ)n的值,以便incrementer稍后可以訪問它。與此同時(shí),程序還會(huì)保存多份n的拷貝,雖然這些值應(yīng)該在makeIncrementer返回后就消失,但在這個(gè)情況下卻繼續(xù)保留下來(lái)給每一個(gè)incrementer對(duì)象使用。這樣的代碼編譯之后會(huì)是什么樣子?closure幕后的真正工作機(jī)理又是什么?這次運(yùn)氣不錯(cuò),我們有一個(gè)后臺(tái)通行證,可以一窺究竟。
一點(diǎn)小常識(shí)往往可以幫大忙。乍一看這些本地變量已經(jīng)不再受限于基本的域限制并擁有無(wú)限的生命周期了。于是可以得出一個(gè)很明顯的結(jié)論:它們已經(jīng)不是存在棧上,而是堆上了8。這么說(shuō)來(lái)closure的實(shí)現(xiàn)和前面討論過(guò)的函數(shù)差不多,只不過(guò)closure多了一個(gè)額外的引用指向其外部的變量而已:

 class some_function_t {
   SymbolTable parentScope;

   // ...
}

當(dāng)closure需要訪問不在它本地域的變量時(shí),就可以通過(guò)這個(gè)引用到更外一層的父域中尋找該變量。謎底揭開了!closure將函數(shù)編程與面向?qū)ο蟮姆椒ńY(jié)合了起來(lái)。下一次為了保存并傳遞某些狀態(tài)而創(chuàng)建類的時(shí)候,想想closure。它能在運(yùn)行時(shí)從相應(yīng)的域中獲得變量,從而可以把該變量當(dāng)初“成員變量”來(lái)訪問,也因?yàn)檫@樣,就不再需要去創(chuàng)建一個(gè)成員變量了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)