9.5 传名参数
前一节的withPrintWriter方法跟语言内建的控制结构(比如if和while)不同,花括号中间的代码接收一个入参。传入withPrintWriter的函数需要一个类型为PrintWriter的入参,这个入参就是下面代码当中的“writer =>”:
不过假如你想要实现那种更像是if或while的控制结构,没有值需要传入花括号中间的代码,该怎么办呢?为了帮助我们应对这样的场景,Scala提供了传名参数(by-name parameter)。
我们来看一个具体的例子,假定你想要实现一个名为myAssert的断言结构。[3]这个myAssert函数将接收一个函数值作为输入,然后通过一个标记来决定如何处理。如果标记位打开,myAssert将调用传入的函数,验证这个函数返回了true。而如果标记位关闭,那么myAssert将什么也不做。
如果不使用传名参数,你可能会这样来实现myAssert:
这个定义没有问题,不过用起来有些别扭:
你大概更希望能不在函数字面量里写空的圆括号和=>符号,而是直接这样写:
传名参数就是为此而生的。要让参数成为传名参数,需要给参数一个以=>开头的类型声明,而不是() =>。例如,可以像这样将myAssert的predicate参数转成传名参数:把类型“() => Boolean”改成“=> Boolean”。示例9.5给出了具体的样子:
示例9.5 使用传名参数
现在已经可以对要做断言的属性去掉空的参数列表了。这样做的结果就是byNameAssert用起来跟使用内建的控制结构完全一样:
对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。
你可能会好奇为什么不能简单地用老旧的Boolean来作为其参数的类型声明,就像这样:
这种组织方式当然也是合法的,boolAssert用起来也跟之前看上去完全一样:
不过,这两种方式有一个显著的区别需要注意。由于boolAssert的参数类型为Boolean,在boolAssert(5 > 3)圆括号中的表达式将“先于”对boolAssert的调用被求值。而由于byNameAssert的predicate参数类型是=> Boolean,在byNameAssert(5 > 3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5 > 3求值,传入byNameAssert的是这个函数值。
因此,两种方式的区别在于如果断言被禁用,你将能够观察到boolAssert的圆括号当中的表达式的副作用,而用byNameAssert则不会。例如,如果断言被禁用,那么我们断言“x / 0 == 0”的话,boolAssert会抛异常:
而对同样的代码用byNameAssert来做断言的话,不会有异常抛出: