上篇我們使用柯里化函數定義一個控制機構 withPrintWriter,它使用時語法調用有如 Scala 內置的控制結構:
val file = new File("date.txt")
withPrintWriter(file){
writer => writer.println(new java.util.Date)
}
不過仔細看一看這段代碼,它和 scala 內置的 if 或 while 表達式還是有些區(qū)別的,withPrintWrite r的{}中的函數是帶參數的含有“writer=>”。 如果你想讓它完全和 if 和 while 的語法一致,在 Scala 中可以使用傳民參數來解決這個問題。
注
:我們知道通常函數參數傳遞的兩種模式,一是傳值,一是引用。而這里是第三種按名稱傳遞。
下面我們以一個具體的例子來說明傳名參數的用法:
var assertionsEnabled=true
def myAssert(predicate: () => Boolean ) =
if(assertionsEnabled && !predicate())
throw new AssertionError
這個 myAssert 函數的參數為一個函數類型,如果標志 assertionsEnabled 為 True 時,mymyAssert 根據 predicate 的真假決定是否拋出異常,如果 assertionsEnabled 為 false,則這個函數什么也不做。
這個定義沒什么問題,但調用起來看起來卻有些別扭,比如:
myAssert(() => 5 >3 )
還需要 ()=>,你可以希望直接使用 5>3,但此時會報錯:
scala> myAssert(5 >3 )
<console>:10: error: type mismatch;
found : Boolean(true)
required: () => Boolean
myAssert(5 >3 )
此時,我們可以把按值傳遞(上面使用的是按值傳遞,傳遞的是函數類型的值)參數修改為按名稱傳遞的參數,修改方法,是使用=>開始而不是 ()=>來定義函數類型,如下:
def myNameAssert(predicate: => Boolean ) =
if(assertionsEnabled && !predicate)
throw new AssertionError
此時你就可以直接使用下面的語法來調用 myNameAssert:
myNameAssert(5>3)
此時就和 Scala 內置控制結構一樣了,看到這里,你可能會想我為什么不直接把參數類型定義為 Boolean,比如:
def boolAssert(predicate: Boolean ) =
if(assertionsEnabled && !predicate)
throw new AssertionError
調用也可以使用
boolAssert(5>3)
和 myNameAssert 調用看起來也沒什么區(qū)別,其實兩者有著本質的區(qū)別,一個是傳值參數,一個是傳名參數,在調用 boolAssert(5>3)時,5>3 是已經計算出為 true,然后傳遞給 boolAssert 方法,而 myNameAssert(5>3),表達式 5>3 沒有事先計算好傳遞給 myNameAssert,而是先創(chuàng)建一個函數類型的參數值,這個函數的 apply 方法將計算5>3,然后這個函數類型的值作為參數傳給 myNameAssert。
因此這兩個函數一個明顯的區(qū)別是,如果設置 assertionsEnabled 為 false,然后試圖計算 x/0 ==0,
scala> assertionsEnabled=false
assertionsEnabled: Boolean = false
scala> val x = 5
x: Int = 5
scala> boolAssert ( x /0 ==0)
java.lang.ArithmeticException: / by zero
... 32 elided
scala> myNameAssert ( x / 0 ==0)
可以看到 boolAssert 拋出 java.lang.ArithmeticException: / by zero 異常,這是因為這是個傳值參數,首先計算 x /0 ,而拋出異常,而 myNameAssert 沒有任何顯示,這是因為這是個傳名參數,傳入的是一個函數類型的值,不會先計算 x /0 ==0,而在 myNameAssert 函數體內,由于 assertionsEnabled 為 false,傳入的 predicate 沒有必要計算(短路計算),因此什么也不會打印。如果我們把 myNameAssert 修改下,把 predicate 放在前面:
scala> def myNameAssert1(predicate: => Boolean ) =
| if( !predicate && assertionsEnabled )
| throw new AssertionError
myNameAssert1: (predicate: => Boolean)Unit
scala> myNameAssert1 ( x/0 ==0)
java.lang.ArithmeticException: / by zero
at $anonfun$1.apply$mcZ$sp(<console>:11)
at .myNameAssert1(<console>:9)
... 32 elided
這個傳名參數函數也拋出異常(你可以想想是為什么?)
前面的 withPrintWriter 我們暫時沒法使用傳名參數,去掉 writer=>,否則就難以實現“租賃模式”,不過我們可以看看下面的例子,設計一個 withHelloWorld 控制結構,這個 withHelloWorld 總打印一個“hello,world”
import scala.io._
import java.io._
def withHelloWorld ( op: => Unit) {
op
println("Hello,world")
}
val file = new File("date.txt")
withHelloWorld{
val writer=new PrintWriter(file)
try{
writer.println(new java.util.Date)
}finally{
writer.close()
}
}
withHelloWorld {
println ("Hello,Guidebee")
}
Hello,world
Hello,Guidebee
Hello,world
可以看到 withHelloWorld 的調用語法和 Scala 內置控制結構非常象了。