1. 概述
在本教程中,我们将深入探讨 Scala 中的高阶函数(Higher-Order Functions)。
2. 高阶函数定义
简单来说,一个函数如果满足以下任意一个或两个条件,就可以称为高阶函数:
- ✅ 接收一个或多个函数作为参数
- ✅ 返回一个函数作为结果
由于在 Scala 中,函数是一等公民,所以这种能力是天然支持的。如果你熟悉 Java 8+ 的 Lambda 表达式,那么这个概念对你来说应该不会陌生。
接下来,我们会先看一些 Scala 标准库提供的经典高阶函数示例,然后我们也会动手写一个自己的高阶函数。
3. 函数作为参数传入
我们从几个 Scala 最常用的高阶函数 开始,这些函数都接收函数作为参数。
3.1. map
map 是用于将一个集合转换为另一个集合的函数。我们需要做的就是描述如何对每个元素进行转换。
假设我们有一个包含若干人名的序列,目标是给每个人名加上前缀 "sir"
:
val expected = Seq("sir Alex Ferguson", "sir Bobby Charlton", "sir Frank Lampard")
val names = Seq("Alex Ferguson", "Bobby Charlton", "Frank Lampard")
val sirNames = names.map(name => "sir " + name)
assertEquals(expected, sirNames)
这里我们使用了一个匿名函数来定义转换逻辑。当然,也可以用一个已定义好的函数:
def prefixWithSir(name: String) = "sir " + name
val sirNames = names.map(prefixWithSir)
这种方式特别适合函数逻辑较复杂的情况。
3.2. filter
filter 用来从集合中筛选出我们感兴趣的元素。
比如我们只想保留以 "John"
开头的名字:
val expected = Seq("John O'Shea", "John Hartson")
val names = Seq("John O'Shea", "Aiden McGeady", "John Hartson")
val johns = names.filter(name => name.matches("^John .*"))
assertEquals(expected, johns)
通过提供一个合适的 谓词函数(predicate),我们就得到了只包含符合条件元素的新集合。
3.3. reduce
reduce 与前面的函数不同,它会将多个类型为 T 的元素 合并成一个类型为 T 的结果。
比如我们要计算一组收入的总和:
val expected = 2750
val earnings = Seq(1000, 1300, 450)
val sumEarnings = earnings.reduce((acc, x) => acc + x)
assertEquals(expected, sumEarnings)
这里我们传递了一个函数,它包含两个参数:累加器(accumulator) acc 和当前元素 x。
⚠️ 累加器负责维护计算过程中的状态。
默认情况下,reduce 调用的是 reduceLeft,即从左到右遍历。如果想反向处理,可以使用 reduceRight。
3.4. fold
fold 与 reduce 类似,但它允许我们 为累加器设置初始值,从而控制返回结果的类型。
举个例子,我们有多个字符串,想统计它们总共有多少个单词:
val expected = 6
val strings = Seq("bunch of words", "just me", "it")
val numberOfWords = strings.foldLeft(0)((acc, x) => acc + x.split(" ").size)
assertEquals(expected, numberOfWords)
注意我们使用的是 foldLeft 方法,和 reduceLeft 一样,它从集合的开头开始处理。我们传入了初始值 0
作为累加器的起点。
其余部分与 reduce 的使用方式一致。
4. 函数返回函数
前面我们讨论的都是接收函数作为参数的高阶函数,现在我们来看一下 返回函数的高阶函数。
我们通过 Scala 的模式匹配 API 来实现一个返回函数的高阶函数:
def mathOperation(name: String): (Int, Int) => Int = (x: Int, y: Int) => {
name match {
case "addition" => x + y
case "multiplication" => x * y
case "division" => x/y
case "subtraction" => x - y
}
}
def add: (Int, Int) => Int = mathOperation("addition")
def mul: (Int, Int) => Int = mathOperation("multiplication")
def div: (Int, Int) => Int = mathOperation("division")
def sub: (Int, Int) => Int = mathOperation("subtraction")
assertEquals(15, add(10, 5))
assertEquals(50, mul(10, 5))
assertEquals(2, div(10, 5))
assertEquals(5, sub(10, 5))
我们定义了一个 mathOperation 函数,它接收一个操作名称作为参数,并返回一个执行对应操作的函数。返回类型 (Int, Int) => Int
表示返回的是一个接受两个 Int
参数并返回 Int
的函数。
接着我们创建了几个函数变量:add、mul、div 和 sub,并验证它们的行为是否符合预期。
💡 这种写法其实和面向对象中的工厂模式非常相似。
5. 总结
在这篇教程中,我们学习了 Scala 中的高阶函数。它们是函数式编程中的基础工具。
高阶函数的强大之处在于,它们能让我们写出简洁、无样板代码、且可读性强的程序。
一如既往,本文中的代码片段可以在 GitHub 仓库 中找到。