程式扎記: [Scala 小學堂] Scala Gossic : 了解更多 - 定義函式 (閉包)

標籤

2016年5月17日 星期二

[Scala 小學堂] Scala Gossic : 了解更多 - 定義函式 (閉包)

轉載自 這裡 
前言 : 
Scala 是一個可直譯、編譯、靜態、運行於 JVM 之上、可與 Java 互操作、融合物件導向編程特性與函式編程風格的程式語言. Scala 本身具有擴充性,不過這必須了解更多語法特性與細節. 

閉包(Closure) : 
所謂的閉包Closure),是指一個函式物件(或函式值)在建立時,綁定了當時作用範圍(Scope)下有效的自由變數(Free variable)。所 以支援閉包的語言,必須有支援一級函式(First-class function),建立函式物件並不等於建立閉包,建立函式物件時同時綁定了某個([color=brown]些)自由變數,該函式物件才稱之為閉包[/color]. 那麼什麼是自由變數?自由變數是指對於函式而言,既非區域變數也非參數的變數,像區域變數或參數,其作用範圍基本上是在函式定義的範 圍中,所以是有界變數(Bound variable)。舉個例子來說 : 
- Closure1.scala 代碼 :
  1. // 對 doSome 來說,x 是 bound variable  
  2. def doSome() = {  
  3.     val x = 10  
  4.     // 對 f 來說,x 是 free variable,y 是 bound variable  
  5.     val f = (y: Int) => x + y  
  6.     f  
  7. }  

上面 doSome 的例子中,(y: Int) => x + y 形成了一個閉包,如果你單看 : 
  1. val f = (y: Int) => x + y  
x 沒有任何的意義,其語意依賴於當時 x 作用範圍下的意義,閉包必須在自由變數的有效範圍時定義。在上面 doSome 的例子中,(y: Int) => x + y 形成了一個閉包,因為這個函式物件將自由變數 x 關閉closed over在函式物件自己的作用範圍中只有函式物件還有效,被關閉的自由變數就有效,相當於延長了自由變數的作用範圍. 舉個例子來說 : 
  1. val foo = doSome()  
  2. println(foo(20))    // 顯示 30  
  3. println(foo(30))    // 顯示 40  
由 於 doSome 函式傳回了函式物件,上例中將傳回的函式物件指定給 foo,就 doSome 而言已經執行完畢,單看 x 的話,理應 x 已結束其生命週期,但由於 doSome 中建立了閉包並傳回,x 被關閉在閉包中,所以 x 的生命週期就與閉包的生命週期相同了,如上例所示,呼叫 foo(20) 結果就是 10+20(因 為被閉關的 x 值是10),呼叫 foo(30)結果就是 10+30. 這裡請注意!閉包關閉的是自由變數,而不是自由變數所參考的值。下面這個範例可以證明 : 
- Closure2.scala 代碼 :
  1. def doOther() = {  
  2.     var x = 10  
  3.     val f = x + (_ : Int)  // 佔位字元語法  
  4.     x = 100  
  5.     f  
  6. }  
  7.   
  8. val foo = doOther()  
  9. println(foo(20))      // 顯示 120  
  10. println(foo(30))      // 顯示 130  

在建立閉包時,綁定了 x 變數,而不是數值 10(x 變數的值),也因此 doOther 之後改變了 x 變數的值,而後傳回閉包給 foo 參數後,範例顯示的結果分別是 100+20 與 100+30。由於閉包綁定的是變數,所以你也可以在閉包中改變了變數值 : 
  1. var sum = 0  
  2. val arr = Array(12345)  
  3. arr.foreach(sum += _)  
  4. println(sum)               // 顯示 15  
你可能會有疑問的是,如果閉包關閉了某個自由變數,使得該自由變數的生命週期得以延長,那麼這個會怎麼樣 : 
  1. def doOther() = {  
  2.     var x = 10  
  3.     val f = () => { x -= 1; x }  
  4.     f  
  5. }  
  6.   
  7. val f1 = doOther()  
  8. val f2 = doOther()  
  9.   
  10. println(f1())    // 顯示 9  
  11. println(f2())    // 顯示?  
在這個範例中,doOther 被呼叫了兩次(或更多次),doOther 中的閉包關閉了x,並對其執行了遞減。呼叫了 f1 時,x 會被遞減 1,所以顯示 9,這沒有問題,那麼呼叫 f2()後,結果是多少? 像這類的例子,其實結果是很一致的,關閉的是建立閉包時有效範圍下的自由變數。以上例來說,第一次呼叫 doOther 時,建立了 x 變數,指定值給 x 變數,而後建立閉包將之關閉。第二次呼叫doOther時,建立了新的 x 變數,指定值給 x 變數,而後建立閉包將之關閉。所以 f1 與 f2 關閉的根本是不同作用範圍的x變數也就是該次呼叫 doOther時所建立的 x 變數)。所以上例中,呼叫 f2 之後顯示的值仍是 9. 下面這個也是個類似的例子 : 
- Closure3.scala 代碼 :
  1. def doSome(x: Int) = (a: Int) => x + a  
  2.   
  3. val f1 = doSome(100)    // 閉包綁定的是該次呼叫時所建立的x參數  
  4. val f2 = doSome(200)    // 閉包綁定的是該次呼叫時所建立的x參數  
  5. println(f1(10))    // 顯示 110  
  6. println(f2(10))    // 顯示 210  

雖然沒有指出,不過上一個主題 一級函式(First-class function) 的最後一個範例,已經應用了閉包來解決問題.

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!