程式扎記: [Scala 小學堂] Scala Gossic : 了解更多 - 定義函式 (以名呼叫參數)

標籤

2016年6月13日 星期一

[Scala 小學堂] Scala Gossic : 了解更多 - 定義函式 (以名呼叫參數)

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

以名呼叫參數(By-name parameter): 
到目前為止,所定義函式若有參數,則呼叫時必須先運算出引數值方可呼叫函式。例如 : 
def sum(a: Int, b: Int) = a + b
println(sum(1 + 2, 3 + 4))
 // 顯示 10

在呼叫 sum 函式之前,1+2 與 3+4 都會先運算出結果,然後以 sum(3, 7) 來呼叫函式,a 與 b 參數稱之為以值呼叫參數(By-value paramenter. 來考慮一種情況,你想開發一個函式如下 : 
- ByNameParam1.scala 代碼 : 
  1. def unless(cond: Boolean, func: () => Any) = {  
  2.     if(!cond) {  
  3.         func()  
  4.     }  
  5. }  
  6.   
  7. unless(false, () => println("XD"))  
  8. unless(true, () => println("Orz"))  

這個函式的作用是,除非 cond 條件成立(也就是 true),否則就執行所傳入的函式物件(也就是 if 的相反). 所傳入的函式物件,並沒有定義參數,不過 () => 仍不能省略,也就是你不可以寫成 : 
  1. unless(false, println("XD"))  
如果你想要省略 ()=> 的撰寫,則可以這麼宣告 : 
- ByNameParam2.scala 代碼 :
  1. def unless(cond: Boolean, expr: => Any) = {  
  2.     if(!cond) {  
  3.         expr  
  4.     }  
  5. }  
  6. unless(false, println("XD"))  
  7. unless(true, println("Orz"))  

在上例中,expr 的型態是=>Any,稱之為以名呼叫參數By-name parameter),注意到在呼叫 unless 函式時,直接寫下了 println("XD") 這樣的運算式,省略了()=> 的撰寫。事實上,省略 ()=>並不是這個範例的重點,重點是在於println("XD")不會被馬上執行,真正的執行是在 cond 為false,也就是 unless 中 if 結果為true時. 以名呼叫參數正如其名,給予所指定的運算式一個名稱,以這個名稱代替運算式的執行結果來呼叫函式,真正的運算式執行,則是在你所定義的函式之中. 注意!以名呼叫參數並不是函式物件,它是運算式的代表名稱,所以你不可以這麼寫 : 
  1. def unless(cond: Boolean, expr: => Any) = {  
  2.     if(!cond) {  
  3.         expr()   // 不能有括號  
  4.     }  
  5. }  
在說明 數值操作 時,曾經說明過,&& 與 || 有捷徑運算的作用,但是在 Scala 中,&& 與 || 其實是方法名稱,如何能實現捷徑運算,就是使用以名呼叫參數來實現。以下是個模擬 && 捷徑運算的 and 函式 : 
- ByNameParam3.scala 代碼 :
  1. def and(c1: Boolean, c2: => Boolean) = {  
  2.     if(c1) c2 else c1  
  3. }  
  4.   
  5. println(and(5 > 310 > 3))  // true  
  6. println(and(5 > 310 < 3))  // false  
  7. println(and(1 > 310 > 3))  // false  

以下這個範例可以證明上面的 and 函式確實有捷徑運算的作用 : 
- ByNameParam4.scala 代碼 :
  1. def and(c1: Boolean, c2: => Boolean) = {  
  2.     if(c1) c2 else c1  
  3. }  
  4.   
  5. println(and(5 > 3, {print("run.. "); 1 > 3}))  // 顯示 run.. false  
  6. println(and(1 > 3, {print("run.. "); 10 > 3}))  // 顯示 false  

由於第一個 and 函式呼叫時,5>3 成立,所以必須測試第二個運算式,因而會顯示 run... 訊息,而第二個 and 函式呼叫時,由於 1>3 為 false,不用再測試第二個運算式,直接傳回 false,所以不會顯示 run... 訊息. 在 Scala 中,沒有 until 的功能,也就是除非條件式成立,否則不斷執行迴圈的功能,以下是個模擬 until 功能的函式 : 
- ByNameParam5.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. }  

如果將第一個參數改為 cond: Boolean=trueuntil 將永遠不會停止,因為 count == 0 會先運算為 false 再用以呼叫 until 函式,所以 until 中 !cond 將永遠為 true,所以會一直遞迴呼叫下去,直到遞迴堆疊溢值為止. 事實上,如果搭配 Scala 中 鞣製(Curry) 的特性,可以讓這邊的 unless 與 until 看起來就像是語言內建的語法,這也是 Scala 支援擴充性的一個方式. 

Supplement 
Tutorialspoint - Scala Data Types

沒有留言:

張貼留言

網誌存檔

關於我自己

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