到目前為止,所定義函式若有參數,則呼叫時必須先運算出引數值方可呼叫函式。例如:
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)。
來考慮一種情況,你想開發一個函式如下:
def unless(cond: Boolean, func: () => Any) = {
if(!cond) {
func()
}
}
unless(false, () => println("XD"))
unless(true, () => println("Orz"))
這個函式的作用是,除非cond條件成立(也就是true),否則就執行所傳入的函式物件(也就是if的相反)。所傳入的函式物件,並沒有定義參數,不過() =>仍不能省略,也就是你不可以寫成:
unless(false, println("XD"))
如果你想要省略()=>的撰寫,則可以這麼宣告:
def unless(cond: Boolean, expr: => Any) = {
if(!cond) {
expr
}
}
unless(false, println("XD"))
unless(true, println("Orz"))
在上例中,expr的型態是=>Any,稱之為以名呼叫參數(By-name parameter),注意到在呼叫unless函式時,直接寫下了println("XD")這樣的運算式,省略了()=>的撰寫。事實上,省略()=>並不是這個範例的重點,重點是在於println("XD")不會被馬上執行,真正的執行是在cond為false,也就是unless中if結果為true時。
以名呼叫參數正如其名,給予所指定的運算式一個名稱,以這個名稱代替運算式的執行結果來呼叫函式,真正的運算式執行,則是在你所定義的函式之中。
注意!以名呼叫參數並不是函式物件,它是運算式的代表名稱,所以你不可以這麼寫:
def unless(cond: Boolean, expr: => Any) = {
if(!cond) {
expr() // 不能有括號
}
}
if(!cond) {
expr() // 不能有括號
}
}
在說明 數 值操作 時,曾經說明過,&&與||有捷徑運算的作用,但是在Scala中,&&與||其實是方法名稱,如何能實現捷徑運算,就是使用以名呼叫參數來實現。以下是個模擬&&捷徑運算的and函式:
def and(c1: Boolean, c2: => Boolean) = {
if(c1) c2 else c1
}
println(and(5 > 3, 10 > 3)) // true
println(and(5 > 3, 10 < 3)) // false
println(and(1 > 3, 10 > 3)) // false
以下這個範例可以證明上面的and函式確實有捷徑運算的作用:
def and(c1: Boolean, c2: => Boolean) = {
if(c1) c2 else c1
}
println(and(5 > 3, {print("run.. "); 10 > 3})) // 顯示 run.. false
println(and(1 > 3, {print("run.. "); 10 > 3})) // 顯示 false
由於第一個and函式呼叫時,5>3成立,所以必須測試第二個運算式,因而會顯示run...訊息,而第二個and函式呼叫時,由於1>3為false,不用再測試第二個運算式,直接傳回false,所以不會顯示run...訊息。
在Scala中,沒有until的功能,也就是除非條件式成立,否則不斷執行迴圈的功能,以下是個模擬until功能的函式:
def until(cond: => Boolean, expr: => Unit) {
if(!cond) {
expr
until(cond, expr)
}
}
var count = 10
until(count == 0, {
println(count)
count -= 1
})
如果將第一個參數改為cond: Boolean,until將永遠不會停止,因為count == 0會先運算為false再用以呼叫until函式,所以until中!cond將永遠為true,所以會一直遞迴呼叫下去,直到遞迴堆疊溢值為止。
事實上,如果搭配Scala中 鞣製(Curry) 的特性,可以讓這邊的unless與until 看起來就像是語言內建的語法,這也是Scala支援擴充性的一個方式。