6.2 函数值

在Scala里,可以在函数里创建函数,将函数赋给引用,或者把它们当作参数传给其他函数。Scala的内部实现对这些所谓的函数值进行了处理,把它们创建为特殊类的实例。所以,Scala的函数值是真正的对象。

我们用Scala的函数值重写上面的例子。假定我们要对一个范围内的值执行不同的操作。(比如求和或是对偶数计数。)

先从提取公共代码开始,公共代码就是对范围内的值进行循环的代码,把它们放到一个叫做totalResultOverRange()的方法中:

  1. def totalResultOverRange(number: Int, codeBlock: Int => Int) : Int = {
  2. var result = 0
  3. for (i <- 1 to number) {
  4. result += codeBlock(i)
  5. }
  6. result
  7. }

这里为totalResultOverRange()定义了两个参数。第一个是Int,表示用以循环的值的范围。第二个有些特殊,是一个函数值。参数的名字叫做codeBlock,其类型是一个函数,接收一个Int,返回一个Int①。totalResultOverRange()方法本身的结果也是一个Int

①可以认为这个函数是无副作用的将输入转换为输出。

totalResultOverRange()的方法体里,对一个范围内的值进行循环,对每个元素调用给定的函数(codeBlock)。给定的函数预期接收一个Int,表示范围内的一个元素,返回一个Int,表示对这个元素计算的部分结果。计算或操作本身留给totalResult-OverRange()方法的调用者定义。我们会对调用给定函数值所得的部分结果求和,然后,返回求和的结果。

上面的代码去除了6.1节,“从普通函数迈向高阶函数”那个例子里面的重复。下面就是如何运用totalResultOverRange()这个方法对一个范围内的值进行求和:

  1. println(totalResultOverRange(11, i => i))

这里给方法传了两个实参。第一个实参(11)是循环范围的上限。第二个实参实际上是一个匿名即时(Just In Time)函数,也就是一个没有名字只有实现的函数。在这个例子里,这个函数实现只是简单的把给定的参数返回。=>将左边的参数列表同右边的实现分开。Scala能够从totalResultOverRange()的参数列表中推演出参数(i)的类型是Int。如果参数类型或是结果类型与预期不符,Scala就会报错。

如果要对范围内的偶数进行计数而非求和,可以这样调用方法:

  1. println(totalResultOverRange(11, i => if (i%2 == 0)1 else 0))

如果要对奇数进行计数,可以像下面这样调用方法:

  1. println(totalResultOverRange(11, i => if (i%2 != 0)1 else 0))

Scala可以接收任意个函数值作参数,而且这种作为参数的函数值可以放在任意的位置,而不仅仅是在最后一个(也就是尾部)。

使用了函数值,让代码DRY②就相当容易了。把公共代码收集到一个函数里,差异的部分变成了方法调用的实参。接收函数值的方法在Scala程序库里很常见,你会在第8章“使用容器”中见到。在Scala中,如果我们愿意的话,还可以传递多个参数,定义实参类型。这些都是很容易的,下面很快就会看到。

②参见Andy Hunt和David Thomas的《程序员修炼之道》[HT00],更详细的了解不重复自己(Don't Repeat Yourself,DRY)原则。