Scala编程(第4版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

9.5 传名参数

前一节的withPrintWriter方法跟语言内建的控制结构(比如ifwhile)不同,花括号中间的代码接收一个入参。传入withPrintWriter的函数需要一个类型为PrintWriter的入参,这个入参就是下面代码当中的“writer =>”:

不过假如你想要实现那种更像是ifwhile的控制结构,没有值需要传入花括号中间的代码,该怎么办呢?为了帮助我们应对这样的场景,Scala提供了传名参数(by-name parameter)。

我们来看一个具体的例子,假定你想要实现一个名为myAssert的断言结构。[3]这个myAssert函数将接收一个函数值作为输入,然后通过一个标记来决定如何处理。如果标记位打开,myAssert将调用传入的函数,验证这个函数返回了true。而如果标记位关闭,那么myAssert将什么也不做。

如果不使用传名参数,你可能会这样来实现myAssert

这个定义没有问题,不过用起来有些别扭:

你大概更希望能不在函数字面量里写空的圆括号和=>符号,而是直接这样写:

传名参数就是为此而生的。要让参数成为传名参数,需要给参数一个以=>开头的类型声明,而不是() =>。例如,可以像这样将myAssertpredicate参数转成传名参数:把类型“() => Boolean”改成“=> Boolean”。示例9.5给出了具体的样子:

示例9.5 使用传名参数

现在已经可以对要做断言的属性去掉空的参数列表了。这样做的结果就是byNameAssert用起来跟使用内建的控制结构完全一样:

对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。

你可能会好奇为什么不能简单地用老旧的Boolean来作为其参数的类型声明,就像这样:

这种组织方式当然也是合法的,boolAssert用起来也跟之前看上去完全一样:

不过,这两种方式有一个显著的区别需要注意。由于boolAssert的参数类型为Boolean,在boolAssert(5 > 3)圆括号中的表达式将“先于”对boolAssert的调用被求值。而由于byNameAssertpredicate参数类型是=> Boolean,在byNameAssert(5 > 3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5 > 3求值,传入byNameAssert的是这个函数值。

因此,两种方式的区别在于如果断言被禁用,你将能够观察到boolAssert的圆括号当中的表达式的副作用,而用byNameAssert则不会。例如,如果断言被禁用,那么我们断言“x / 0 == 0”的话,boolAssert会抛异常:

而对同样的代码用byNameAssert来做断言的话,不会有异常抛出: