5.7 Scala的函数式编程功能
前面说过,函数式编程的思维模式与命令式编程不同。这里介绍几个与函数式编程相关的主题:
- 使用函数遍历集合;
- 映射-过滤-归约设计模式;
- 柯里化。
5.7.1 使用函数遍历集合
在函数式编程中,很少使用for或while循环来遍历数组和集合,并在循环体中处理每个元素。相反,将对数组或集合实例调用一个在内部对其进行遍历的方法,这个方法将一个lambda函数作为参数,并对每个元素调用这个函数:
var a = List[Int]($text-5, 10, 15, 20, 25)a.foreach((x: Int) => println("%03d".format(x)))
这将打印005、010、015、020和025,其中使用了Java类java.lang.String的方法format来确保打印出来的整数包含三位(不够时在前面添加零)。
5.7.2 映射-过滤-归约设计模式
与函数式编程相关的一种著名的设计模式是映射-过滤-归约。下面分别来介绍这些模式:
- 映射;
- 过滤;
归约。
映射——对数据进行变换
需要对数组或集合的每个元素都进行变换时,可使用方法map:
var a = List[Int]($text-1, 2, 3)var b = a.map((x: Int) => 2 * x)println(b)
这将打印List(2, 4, 6)。
方法map将一个这样的lambda函数作为参数,即包含一个类型与数组或集合元素相同的输入参数。在这里,我们创建了一个lambda函数,它接受类型为Int的参数x,并返回2 * x。对于每个元素,函数map都将调用传入的函数,从而创建一个新的列表。
- 过滤——过滤集合或数组中的元素
数组和集合类都实现了方法filter,使用它可将集合或数组中的元素剔除,这是通过传递一个函数实现的:这个函数返回一个布尔值,指出是否要将传递给它的元素保留在过滤后的列表或数组中:
var a = List[Int]($text-100, 150, 200, 300)var b = a.filter((x: Int) => x > 150)println(b)
打印的结果为List(200, 300)。由于100和150都不大于150,因此对于这些元素,传递给filter的函数将返回false,导致它们都不会添加到结果中。
- 归约——执行计算
传递给方法reduce的lambda函数接受两个参数:初值和当前元素。返回的值将作为下次调用的初值。下面的示例将元素的值累加:
var a = List[Int]($text-10, 20, 30, 40, 50)var b = a.reduce((x: Int, y: Int) => x + y)println(b)
这将打印结果150。再来看一个示例,它使用了Scala运算符max:
var a = List[Int]($text-100, 2, 30, 60, 555)var b = a.reduce((x: Int, y: Int) => x max y)println(b)
这将打印555。运算符max返回两个值中较大的那个。正如你可能预期的,Scala还提供了运算符min。
与本章介绍的其他所有运算符一样,在
Int类中,运算符min和max也是以方法的方式实现的。
5.7.3 柯里化
在Scala中,可向方法或函数提供多个参数列表:
class CurryingTest {def curryingMethod(a: Int, b: Int)(c: Int): Int = {a * b * c}}
这被称为柯里化(currying)。在上述方法定义中,有两组输入参数,其中一组包含参数a和b,而另一组只包含参数c。调用这个方法时,要指定所有的参数,必须像下面这样做:
var c = new CurryingTest()var result = c.curryingMethod(2, 3)(4)println(result)
与预期的一样,这将返回24。如果只能在程序中这样调用这个方法,柯里化将毫无意义。在需要将函数传递给方法或函数时(这在函数式编程中很常见),柯里化很有用。下面是一个这样的示例:
def doCurrying(x: Int, fun: Int => Int): Int = {fun(x)}var result = doCurrying(30, c.curryingMethod(10, 20))println(result)
这将打印6000。下面来粗略地解释一下这些代码。
(1) 方法doCurrying接受两个参数。
x:一个Int实例。fun:接受一个Int参数并返回一个Int实例的函数。
(2) 函数doCurrying调用函数fun,将x作为输入参数传递给它,并返回函数fun的值。
(3) 调用函数doCurrying时,Scala创建一个临时的匿名(意味着没有名称)函数,这个函数使用第一组参数(a=10和b=20)调用传入的方法curryingMethod。创建的函数还需要一组参数(在这里,这组参数值只包含参数c),这样它才能执行curryingMethod。因此,生成的函数需要一个Int输入参数。
(4) 由于生成的匿名临时函数的签名与定义Int => Int兼容(接受一个Int输入参数并返回一个Int值),Scala编译器将这个生成的函数作为doCurrying的输入参数。
(5) 方法doCurrying执行传递给它的函数(这里是生成的匿名函数),并将x作为输入参数传递给它。至此,两组参数都有了,生成的匿名函数可以使用全部三个参数调用方法c.curryingMethod了。编译器知道这将返回一个Int,与doCurrying的返回类型相同。
与本章介绍的其他所有运算符一样,在