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

標籤

2016年6月13日 星期一

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

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

鞣製(Curry):
來看到Scala中以下的函式定義與使用 :
def sum(a: Int, b: Int) = a + b
println(sum(1, 2))
 // 顯示 3

如果改為以下的函式定義與使用,其結果也相同 :
def sum(a: Int)(b: Int) = a + b
println(sum(1)(2))
 // 顯示 3

第二個範例其實是使用了鞣製(Curry)的特性。所謂鞣製,是指將接受多個參數的函式例如第一個範例的 sum 接受兩個參數,改為接受單一參數的函式也就是第二個範例的 sum(a: Int),在函式執行過後傳回一個函式物件再套用剩下的參數也就是第二個範例的(b: Int)),就像是將兩個函式鞣製在一起, 所以第二個範例,相當於以下的作用 :
- Cury1.scala 代碼 :
  1. def sum(a: Int) = (b: Int) => a + b  
  2. println(sum(1)(2))    // 顯示 3  

不過這只是個比喻,實際上你要取得呼叫鞣製函式的傳回函式物件,並不能像以下的範例 :
def sum(a: Int)(b: Int) = a + b
val s = sum(1)
 // 這是錯的 missing arguments for method sum

若你真的要取得鞣製函式的傳回函式物件,可以使用 部份套用函式(Partially applied function) 的方式 :
- Cury2.scala 代碼 :
  1. def sum(a: Int)(b: Int) = a + b  
  2. val s = sum(1)_  
  3. println(s(2))       // 顯示 3  

在 Scala 中,結合鞣製函式的特性,可以讓你定義出一些函式,在呼叫使用時,感覺像是內建語法的一部份。要真正體驗這個特性之前,你還得先了解一件事,就是在Scala中,如果函式只接受一個引數,則引數列可以撰寫 () 或 {},例如以下的範例都會顯示"XD" :
  1. println("XD")  
  2. println{"XD"}  
了解這個特性之後,你可以知道,先前的sum範例也可以 :
- Cury3.scala 代碼 :
  1. def sum(a: Int)(b: Int) = a + b  
  2. println(sum(1)(2))    // 顯示 3  
  3. println(sum(1){2})    // 顯示 3  

因為sum(1)傳回函式物件後馬上套用第二個引數,而引數值只有一個,所以可以也使用 {} 來傳入引數。接著來看到 以名呼叫參數(By-name parameter) 中的這個範例 :
  1. def unless(cond: Boolean, expr: => Any) = {  
  2.     if(!cond) {  
  3.         expr  
  4.     }  
  5. }  
如果你使用鞣製的特性,把它改為以下的定義 :
  1. def unless(cond: Boolean)(expr: => Any) = {  
  2.     if(!cond) {  
  3.         expr  
  4.     }  
  5. }  
那麼,你可以如下使用 unless 函式 :
- Cury4.scala 代碼 :
  1. def unless(cond: Boolean)(expr: => Any) = {  
  2.     if(!cond) {  
  3.         expr  
  4.     }  
  5. }  
  6.   
  7. val flag = false  
  8. unless(flag) {  
  9.         println("XD")  
  10.         println("Orx")  
  11. }  

單看以上這個語法,你會感覺 unless 就像是內建的語法之一。另一個 以名呼叫參數(By-name parameter) 中的這個範例 :
  1. def until(cond: => Boolean, expr: => Unit) {  
  2.     if(!cond) {  
  3.         expr  
  4.         until(cond, expr)  
  5.     }  
  6. }  
如果你使用鞣製的特性,把它改為以下的定義 :
  1. def until(cond: => Boolean)(expr: => Unit) {  
  2.     if(!cond) {  
  3.         expr  
  4.         until(cond)(expr)  
  5.     }  
  6. }  
那麼你就可以如下使用 until 函式 :
- Cury5.scala 代碼 :
  1. def until(cond: => Boolean)(expr: => Unit) {  
  2.     if(!cond) {  
  3.         expr  
  4.         until(cond)(expr)  
  5.     }  
  6. }  
  7.   
  8. var count = 10  
  9. until(count == 0) {  
  10.     println(count)  
  11.     count -= 1  
  12. }  

單看以上這個語法,你會感覺 until 就像是內建的語法之一。你可以再回憶一下 函式重載、區域函式、重複參數 中,最後一個使用 foldLeft 別名方法 /: 的範例,這是因為 /: 也使用了鞣製的特性 :
def sum(numbers: Int*) = (0 /: numbers) {_ + _}
println(sum(1, 2))
 // 顯示 3


沒有留言:

張貼留言

網誌存檔

關於我自己

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