Scala开发教程(28): 传名参数

jerry Scala 2015年11月25日 收藏

上篇我们使用柯里化函数定义一个控制机构withPrintWriter ,它使用时语法调用有如Scala内置的控制结构:

  1. val file = new File("date.txt")
  2. withPrintWriter(file){
  3. writer => writer.println(new java.util.Date)
  4. }

不过仔细看一看这段代码,它和scala内置的if或while表达式还是有些区别的,withPrintWriter的{}中的函数是带参数的含有“writer=>”。 如果你想让它完全和if和while的语法一致,在Scala中可以使用传民参数来解决这个问题。
注:我们知道通常函数参数传递的两种模式,一是传值,一是引用。而这里是第三种按名称传递。

下面我们以一个具体的例子来说明传名参数的用法:

  1. var assertionsEnabled=true
  2. def myAssert(predicate: () => Boolean ) =
  3. if(assertionsEnabled && !predicate())
  4. throw new AssertionError
  5.  

这个myAssert函数的参数为一个函数类型,如果标志assertionsEnabled为True时,mymyAssert 根据predicate 的真假决定是否抛出异常,如果assertionsEnabled 为false,则这个函数什么也不做。

这个定义没什么问题,但调用起来看起来却有些别扭,比如:

  1. myAssert(() => 5 >3 )

还需要 ()=> ,你可以希望直接使用 5>3,但此时会报错:

  1. scala> myAssert(5 >3 )
  2. <console>:10: error: type mismatch;
  3. found : Boolean(true)
  4. required: () => Boolean
  5. myAssert(5 >3 )
  6.  

此时,我们可以把按值传递(上面使用的是按值传递,传递的是函数类型的值)参数修改为按名称传递的参数,修改方法,是使用 => 开始而不是 ()=>来定义函数类型,如下:

  1. def myNameAssert(predicate: => Boolean ) =
  2. if(assertionsEnabled && !predicate)
  3. throw new AssertionError
  4.  

此时你就可以直接使用下面的语法来调用myNameAssert:

  1. myNameAssert(5>3)

此时就和Scala内置控制结构一样了,看到这里,你可能会想我为什么不直接把参数类型定义为Boolean,比如:

  1. def boolAssert(predicate: Boolean ) =
  2. if(assertionsEnabled && !predicate)
  3. throw new AssertionError

调用也可以使用

  1. boolAssert(5>3)

和myNameAssert 调用看起来也没什么区别,其实两者有着本质的区别,一个是传值参数,一个是传名参数,在调用boolAssert(5>3)时,5>3是已经计算出为true,然后传递给boolAssert方法,而myNameAssert(5>3),表达式5>3没有事先计算好传递给myNameAssert,而是先创建一个函数类型的参数值,这个函数的apply方法将计算5>3,然后这个函数类型的值作为参数传给myNameAssert。

因此这两个函数一个明显的区别是,如果设置assertionsEnabled 为false, 然后试图计算 x/0 ==0,

  1. scala> assertionsEnabled=false
  2. assertionsEnabled: Boolean = false
  3.  
  4. scala> val x = 5
  5. x: Int = 5
  6.  
  7. scala> boolAssert ( x /0 ==0)
  8. java.lang.ArithmeticException: / by zero
  9. ... 32 elided
  10.  
  11. scala> myNameAssert ( x / 0 ==0)
  12.  

可以看到boolAssert 抛出 java.lang.ArithmeticException: / by zero 异常,这是因为这是个传值参数,首先计算 x /0 ,而抛出异常,而 myNameAssert 没有任何显示,这是因为这是个传名参数,传入的是一个函数类型的值,不会先计算x /0 ==0,而在myNameAssert 函数体内,由于assertionsEnabled为false,传入的predicate没有必要计算(短路计算),因此什么也不会打印。如果我们把myNameAssert 修改下,把predicate放在前面:

  1. scala> def myNameAssert1(predicate: => Boolean ) =
  2. | if( !predicate && assertionsEnabled )
  3. | throw new AssertionError
  4. myNameAssert1: (predicate: => Boolean)Unit
  5.  
  6. scala> myNameAssert1 ( x/0 ==0)
  7. java.lang.ArithmeticException: / by zero
  8. at $anonfun$1.apply$mcZ$sp(<console>:11)
  9. at .myNameAssert1(<console>:9)
  10. ... 32 elided
  11.  
  12.  

这个传名参数函数也抛出异常(你可以想想是为什么?)

前面的withPrintWriter 我们暂时没法使用传名参数,去掉writer=>,否则就难以实现“租赁模式”,不过我们可以看看下面的例子,设计一个withHelloWorld控制结构,这个withHelloWorld总打印一个“hello,world?

  1. import scala.io._
  2. import java.io._
  3. def withHelloWorld ( op: => Unit) {
  4. op
  5. println("Hello,world")
  6. }
  7.  
  8.  
  9. val file = new File("date.txt")
  10. withHelloWorld{
  11. val writer=new PrintWriter(file)
  12. try{
  13. writer.println(new java.util.Date)
  14. }finally{
  15. writer.close()
  16. }
  17. }
  18.  
  19. withHelloWorld {
  20. println ("Hello,Guidebee")
  21. }
  22.  
  23. -----
  24.  
  25. Hello,world
  26. Hello,Guidebee
  27. Hello,world
  28.  

可以看到withHelloWorld 的调用语法和Scala内置控制结构非常象了。