6.4 Curry化

Scala里的curry化可以把函数从接收多个参数转换成接收多个参数列表。如果要用同样的一组实参多次调用同一个函数,可以用curry化来减少噪音,让代码更有味道。

我们来看看Scala如何提供curry化的支持。我们要编写的方法不是接收一个参数列表,里面有多个参数,而是有多个参数列表,每个里面只有一个参数。(每个参数列表也可以有多个参数。)也就是说,写的不是def foo(a: Int, b: Int, c: Int) {},而是def foo(a: Int)(b: Int)(c: Int) {}。可以这样调用这个方法,比如,foo(1)(2)(3)foo(1){2}{3},甚至这样foo{1}{2}{3}

我们来考察一下定义成有多个参数列表的方法会怎么样。看看下面这段Scala交互式shell的会话:

  1. scala> def foo(a: Int)(b: Int)(c:Int) {}
  2. foo: (Int)(Int)(Int)Unit
  3. scala> foo _
  4. res1: (Int) => (Int) => (Int) => Unit = <function>
  5. scala>

按照上面的讨论,我们定义了一个函数foo()。然后,调用foo_创建了一个偏应用函数(见6.8节“偏应用函数”),也就是说,这个函数有一个或多个参数未绑定。创建出的偏应用函数可以赋给变量,但在这个例子里,我们并不关心。我们的关注点是交互式shell提供的消息。它显示了三个一连串的转换。链中的每个函数都接收一个Int,返回一个偏应用函数。不过,最后一个的结果是Unit

在curry时创建偏应用函数是Scala的内部事务。从实用的观点来看,它们让我们可以借助于语法糖传递函数值。这样,我们以curry化的形式重写上一节的inject()方法:

FunctionValuesAndClosures/Inject4.scala

  1. def inject(arr: Array[Int], initial: Int)(operation: (Int, Int) => Int) : Int = {
  2. var carryOver = initial
  3. arr.foreach(element => carryOver = operation(carryOver, element))
  4. carryOver
  5. }

上面这个版本的inject()方法和早先的版本之间唯一的差别就在于多个参数列表。第一个参数列表接收两个参数,第二个接收一个,是个函数值。

如此一来,传递函数值就不必再在括号里用以逗号分隔的参数了。我们可以用更为优雅的大括号来调用这个方法:

FunctionValuesAndClosures/Inject4.scala

  1. val array = Array(2, 3, 5, 1, 6, 4)
  2. val sum = inject(array, 0) { (carryOver, elem) => carryOver + elem }
  3. println("Sum of elements in array " + array.toString() + " is" + sum)