- Scala编程(第4版)
- (德)马丁·奥德斯基 (美)莱斯·彭 比尔·文纳斯
- 865字
- 2021-06-10 14:34:23
9.5 传名参数
前一节的withPrintWriter方法跟语言内建的控制结构(比如if和while)不同,花括号中间的代码接收一个入参。传入withPrintWriter的函数需要一个类型为PrintWriter的入参,这个入参就是下面代码当中的“writer =>”:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-1.jpg?sign=1739044490-kWnEYfDKUO2QDaYVGg94hCa3kDUR7C1O-0-810b888fd17633954863013c912f2013)
不过假如你想要实现那种更像是if或while的控制结构,没有值需要传入花括号中间的代码,该怎么办呢?为了帮助我们应对这样的场景,Scala提供了传名参数(by-name parameter)。
我们来看一个具体的例子,假定你想要实现一个名为myAssert的断言结构。[3]这个myAssert函数将接收一个函数值作为输入,然后通过一个标记来决定如何处理。如果标记位打开,myAssert将调用传入的函数,验证这个函数返回了true。而如果标记位关闭,那么myAssert将什么也不做。
如果不使用传名参数,你可能会这样来实现myAssert:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-2.jpg?sign=1739044490-a6FNWC8tcSzSqgVBv1bAL5NLDUbBIlFV-0-7c81a3b94ae187095853b60042339154)
这个定义没有问题,不过用起来有些别扭:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-3.jpg?sign=1739044490-IbfaoXtv2XdgiZHU1q5KKs9027nuROTY-0-540acbc68a33e162a9c6ddcfb78f8e90)
你大概更希望能不在函数字面量里写空的圆括号和=>符号,而是直接这样写:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-1.jpg?sign=1739044490-AIYominWqAIOaJm3jNrXzfUtyRFcnD3F-0-b6a3c8c5d7dbc7f28c020698484c1e02)
传名参数就是为此而生的。要让参数成为传名参数,需要给参数一个以=>开头的类型声明,而不是() =>。例如,可以像这样将myAssert的predicate参数转成传名参数:把类型“() => Boolean”改成“=> Boolean”。示例9.5给出了具体的样子:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-2.jpg?sign=1739044490-aGxWN7PbulPXTQAeUHYXDw1SrSlW4wAf-0-dd283dfe2cb5e5b5506448d126837162)
示例9.5 使用传名参数
现在已经可以对要做断言的属性去掉空的参数列表了。这样做的结果就是byNameAssert用起来跟使用内建的控制结构完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-3.jpg?sign=1739044490-5hYwBpqQSmaFqdjANX4MeWUmtIHM2lUi-0-03e3cddd89342f3bdd173d548fda3f1b)
对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。
你可能会好奇为什么不能简单地用老旧的Boolean来作为其参数的类型声明,就像这样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-4.jpg?sign=1739044490-XGtVasmOxdYb9O4IzxYuJiaUnGnL5lGJ-0-554b7b50be15eb05cd021df326e1a9a2)
这种组织方式当然也是合法的,boolAssert用起来也跟之前看上去完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-5.jpg?sign=1739044490-lz8XGUlXnKnuXGR6t8ief4sDOK6oJIVs-0-7b751a9acdfc3efd623cb45543e1f6a3)
不过,这两种方式有一个显著的区别需要注意。由于boolAssert的参数类型为Boolean,在boolAssert(5 > 3)圆括号中的表达式将“先于”对boolAssert的调用被求值。而由于byNameAssert的predicate参数类型是=> Boolean,在byNameAssert(5 > 3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5 > 3求值,传入byNameAssert的是这个函数值。
因此,两种方式的区别在于如果断言被禁用,你将能够观察到boolAssert的圆括号当中的表达式的副作用,而用byNameAssert则不会。例如,如果断言被禁用,那么我们断言“x / 0 == 0”的话,boolAssert会抛异常:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-211-1.jpg?sign=1739044490-Jddf9oMNX2Lt2hjWSHAZ0F3gzFvZULdq-0-543a87ee817870b29d77d8fc17967491)
而对同样的代码用byNameAssert来做断言的话,不会有异常抛出:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-211-2.jpg?sign=1739044490-ttyF68euVAYhJsFKFJOnvSLku5U7i34K-0-4ea1034135a2a53ee2cef54577da7ef4)