Scala 函数式编程特性
Scala 函数式编程核心特性
Scala 是一门融合了面向对象与函数式编程范式的静态类型语言。其函数式特性让代码更简洁、更易于测试和并发。本章将逐步解析 Scala 中最重要的函数式编程特性,即使你只有基础编程背景也能轻松上手。
1. 不可变性与表达式思维
函数式编程的基石是不可变性:一旦对象被创建,其状态就不能改变。在 Scala 中,推荐使用 val 而非 var。
val list = List(1, 2, 3) // 不可变列表
// list(0) = 10 // 编译错误, List 是不可变的
val newList = 0 :: list // 返回新的 List(0, 1, 2, 3),原 list 不变
代码应尽量以表达式形式编写,每个语句都返回一个值,没有副作用。例如,if 本身就是一个表达式:
val result = if (x > 0) "正数" else "非正数" // 返回 String
2. 将函数视为一等公民
在 Scala 中,函数是一等值,可以像变量一样被传递、赋值和组合。
函数字面量与匿名函数
我们可以直接定义一个匿名函数:
val add = (x: Int, y: Int) => x + y
println(add(3, 5)) // 输出 8
add 的类型是 (Int, Int) => Int,它就是一个可以反复使用的值。
高阶函数
高阶函数是指接受函数作为参数,或返回函数的函数。Scala 集合库中的 map、filter、reduce 等都是典型例子。
val numbers = List(1, 2, 3, 4, 5)
val doubled = numbers.map(x => x * 2) // List(2, 4, 6, 8, 10)
val even = numbers.filter(_ % 2 == 0) // List(2, 4)
val sum = numbers.reduce(_ + _) // 15
占位符语法 _ 进一步简化了代码,_ + _ 相当于 (a, b) => a + b。
3. 闭包与作用域
闭包是指函数引用了其外部作用域的变量,即使外部函数已经执行完毕,这个函数仍能“记住”并访问那些变量。
def multiplier(factor: Int): Int => Int = {
(x: Int) => x * factor // factor 来自外部作用域
}
val triple = multiplier(3)
println(triple(10)) // 30
multiplier 返回的是一个闭包,它捕获了参数 factor。
4. 柯里化与偏应用函数
柯里化
柯里化将原本接收多个参数的函数转化为一系列接收单一参数的函数链。
// 普通函数
def add(x: Int, y: Int) = x + y
// 柯里化版本
def addCurried(x: Int)(y: Int) = x + y
val plusFive = addCurried(5)_ // 偏应用,固定第一个参数
println(plusFive(10)) // 15
Sc ala 中定义柯里化函数只需使用多个参数列表,调用时也可以分步提供参数。
偏应用函数
可以使用下划线 _ 占位,暂时不提供某些参数,从而生成一个接收剩余参数的新函数。
val numbers = List(1, 2, 3)
// 偏应用使用 println 打印每个元素
numbers.foreach(println _) // 等价于 numbers.foreach(println)
5. 模式匹配
Scala 的 match 远比其他语言中的 switch 强大,它能解构各类数据结构,并内建了守卫条件。
def describe(x: Any): String = x match {
case 0 => "零"
case true => "真"
case s: String => s"字符串:$s"
case list: List[_] if list.nonEmpty => s"非空列表,长度 ${list.size}"
case _ => "其他值"
}
println(describe(List(1, 2, 3))) // 非空列表,长度 3
在 Scala 的函数式编程中,模式匹配常用于解构 Option、Either 等容器,避免空指针。
6. Option 与 Either:安全处理缺失值
函数式编程极力避免 null。Scala 提供了 Option[A] 和 Either[A, B] 来显式表示结果可能缺失或失败。
Option
Option 有两个子类:Some(value) 表示有值,None 表示空。
def safeDivide(a: Int, b: Int): Option[Int] =
if (b == 0) None else Some(a / b)
val result = safeDivide(10, 2)
// 模式匹配处理
result match {
case Some(v) => println(s"结果是 $v")
case None => println("除数不能为零")
}
// 或使用高阶函数
println(result.getOrElse(0)) // 如果为 None 返回 0
println(result.map(_ * 2)) // Some(10),对内部值进行映射
map、flatMap、filter 等方法让 Option 可以像容器一样被链式处理。
Either
Either 常用于携带错误信息,约定“右值”(Right)表示成功,“左值”(Left)表示失败。
def safeDivideE(a: Int, b: Int): Either[String, Int] =
if (b == 0) Left("除数不能为零")
else Right(a / b)
val eResult = safeDivideE(10, 0)
eResult match {
case Right(v) => println(v)
case Left(e) => println(s"错误:$e")
}
通过 map、flatMap 组合 Either 时,函数会跳过 Left 状态,仅对 Right 值进行转换,这符合函数式错误传播的“轨道”模式。
7. 不可变集合与常用高阶操作
Scala 默认导入的集合都是不可变的(scala.collection.immutable),提供了丰富的高阶操作,支持声明式编程。
- 映射与过滤:
map、flatMap、filter、filterNot - 折叠与归约:
foldLeft、foldRight、reduce、reduceLeft - 分组与排序:
groupBy、sortWith、sorted - 拉链与遍历:
zip、zipWithIndex、foreach
示例:统计单词频率,全程无副作用。
val text = "hello world hello scala"
val wordCount = text.split("\\s+")
.groupBy(identity)
.view.mapValues(_.length)
.toMap
// Map(hello -> 2, world -> 1, scala -> 1)
8. For 推导式:语法糖下的单子运算
Scala 的 for 不是循环,而是一个推导式,底层转换为 map、flatMap、withFilter 等调用。这使得链式处理 Option、List、Either 等类型时,代码非常清晰。
val optA = Some(10)
val optB = Some(5)
val result = for {
a <- optA
b <- optB
if b != 0
} yield a / b
// result = Some(2)
若任何一个中间步骤为 None,整个结果就是 None。for 推导极大简化了多步验证和组合计算。
9. 尾递归优化
Scala 编译器能够对尾递归函数做优化,避免栈溢出。用递归代替命令式循环是函数式编程的常见模式。
def factorial(n: Int): BigInt = {
@annotation.tailrec
def loop(acc: BigInt, x: Int): BigInt =
if (x <= 1) acc else loop(acc * x, x - 1)
loop(1, n)
}
println(factorial(10000)) // 不会发生栈溢出
@annotation.tailrec 注解可以强制编译器检查是否真的是尾递归。
10. 函数组合与部分应用
通过 compose 和 andThen 可以将多个函数串联。
val f = (x: Int) => x + 1
val g = (x: Int) => x * 2
val h = f andThen g // 先执行 f,再执行 g
println(h(3)) // (3+1)*2 = 8
val h2 = f compose g // 先执行 g,再执行 f
println(h2(3)) // (3*2)+1 = 7
这使得无副作用的管道操作成为可能。
通过掌握以上特性,你将能写出简洁、健壮且易于复用的 Scala 代码,充分发挥函数式编程的优势。