1. 概述
Scala 是一门类型安全的 JVM 编程语言,融合了面向对象和函数式编程的优点,语法简洁、表达力强。它不仅支持 Apache Spark 这样的大数据处理框架,还能根据需求灵活扩展程序规模。这些强大的特性让 Scala 成为一门实用且备受青睐的语言。
本教程将带你了解 使用 Scala 实现函数式编程的核心概念。
2. 什么是函数式编程?
函数式编程是一种以 函数为核心构建模块 的编程范式。在函数式编程中,我们追求使用纯函数和不可变数据。
2.1. 不可变性(Immutability)
不可变性意味着编程时使用的是常量,也就是说变量的值或状态不能被修改。对于对象来说,我们可以创建一个新对象,但不能改变已有对象的状态。因此,不可变对象相比可变对象更加线程安全。我们会在后续章节中看到 Scala 是如何强制实现不可变性的。
2.2. 纯函数(Pure Functions)
纯函数有两个关键属性:
- 对于相同的输入,总是返回相同的结果;
- 没有副作用(Side Effects);
所谓“副作用”是指函数除了返回值之外还做了其他事情,比如修改全局变量、操作 I/O 或者改变传入对象的状态。纯函数只负责返回结果,不做多余的事情。
下一节我们将深入探讨 Scala 中的纯函数。
3. Scala 如何体现函数式特性?
Scala 被称为函数式语言的一个核心特征是:每个函数都是一个值。换句话说,在 Scala 中函数是一等公民(First-Class Citizen)。
虽然在面向对象编程中也可以实现类似结构,例如函数作为参数传递、函数返回函数等,但在函数式编程中这些操作非常自然且常见。
3.1. 函数是一等公民(First-Class Functions)
当我们将函数当作值来处理时,就称其为一等函数。
一般来说,一等函数可以:
✅ 赋值给变量
✅ 作为参数传递给其他函数
✅ 作为其他函数的返回值
而 Scala 默认就支持所有这些特性。
3.2. 高阶函数(Higher-Order Functions)
高阶函数(HOF)与一等函数密切相关。高阶函数具有以下至少一项特征:
✅ 接收一个或多个函数作为参数
✅ 返回一个函数作为结果
通过高阶函数,我们可以像操作普通值一样操作函数。
来看一个接收函数作为参数的例子:
def calcAnything(number: Int, calcFunction: Int => Int): Int = calcFunction(number)
def calcSquare(num: Int): Int = num * num
def calcCube(num: Int): Int = num * num * num
val squareCalculated = calcAnything(2, calcSquare)
assert(squareCalculated == 4)
val cubeCalculated = calcAnything(3, calcCube)
assert(cubeCalculated == 27)
再看一个返回函数的例子:
def performAddition(x: Int, y: Int): Int = x + y
def performSubtraction(x: Int, y: Int): Int = x - y
def performMultiplication(x: Int, y: Int): Int = x * y
def performArithmeticOperation(num1: Int, num2: Int, operation: String): Int = {
operation match {
case "addition" => performAddition(num1, num2)
case "subtraction" => performSubtraction(num1, num2)
case "multiplication" => performMultiplication(num1, num2)
case _ => -1
}
}
val additionResult = performArithmeticOperation(2, 4, "addition")
assert(additionResult == 6)
val subtractionResult = performArithmeticOperation(10, 6, "subtraction")
assert(subtractionResult == 4)
val multiplicationResult = performArithmeticOperation(8, 5, "multiplication")
assert(multiplicationResult == 40)
高阶函数也支持函数组合、匿名函数和 Lambda 表达式等功能。
3.3. 匿名函数(Anonymous Functions)
在使用高阶函数时,经常需要传入一个没有名字的函数,即匿名函数(也叫函数字面量或 Lambda 表达式)。它虽然没有名称,但具备参数列表、函数体和可选的返回类型。
典型的例子包括 Scala 标准库中的 map
、filter
和 fold
等函数:
val numbers = List(1, 2, 3, 4)
val doubled = numbers.map(x => x * 2)
assert(doubled == List(2, 4, 6, 8))
3.4. 闭包(Closures)
闭包本质上也是一种函数,可以是具名函数或匿名函数,可能是纯函数也可能不是。它的特殊之处在于:它可以访问作用域外的自由变量。
举个例子:
val rate = 0.10
val time = 2
def calcSimpleInterest(principal: Double): Double = {
(principal * rate * time) / 100
}
val simpleInterest = 20
assert(calcSimpleInterest(10000) == simpleInterest)
在这个例子中,函数 calcSimpleInterest()
使用了外部定义的变量 rate
和 time
,这两个变量被称为自由变量。这就是闭包。
⚠️ 自由变量不是局部变量,也不是函数的形式参数。
3.5. 柯里化(Currying)
柯里化是指将一个多参数的函数转换成一系列单参数函数的调用链。每一步都返回一个新的函数,直到所有参数都被处理完毕。
形式如下:
result = f(x)(y)(z)
来看一个具体示例:
val multiplication: (Int, Int) => Int = (x, y) => x * y
val curriedMultiplication: Int => Int => Int = x => y => x * y
val multiplicationResult = multiplication(3, 5)
val curriedMultiplicationResult = curriedMultiplication(3)(5)
assert(multiplicationResult == curriedMultiplicationResult)
也可以使用 .curried
方法简化写法:
val conciseCurriedMultiplication: Int => Int => Int = multiplication.curried
val conciseCurriedMultiplicationResult = conciseCurriedMultiplication(3)(5)
assert(multiplicationResult == conciseCurriedMultiplicationResult)
Scala 还提供了一种特殊的语法来支持柯里化:
def addition(x: Int, y: Int): Int = x + y
def curriedAddition(x: Int)(y: Int): Int = x + y
val additionResult = addition(8, 4)
val conciseCurriedAdditionResult = curriedAddition(8)(4)
assert(additionResult == conciseCurriedAdditionResult)
3.6. 偏应用函数(Partially Applied Functions)
在函数式编程中,将参数传递给函数的过程称为“应用函数”。如果一个函数被完整地应用了所有参数,就叫完全应用;如果只传递了一部分参数,则称为偏应用函数。
来看一个实际场景:
def calculateSellingPrice(discount: Double, productPrice: Double): Double = {
(1 - discount/100) * productPrice
}
val discountApplied = calculateSellingPrice(25, _)
val sellingPrice = discountApplied(1000)
assert(sellingPrice == 750)
这里我们假设商店统一打了 25% 折扣,于是我们可以先固定折扣参数,返回一个新函数 discountApplied
,后续只需要传入商品价格即可。
这种方式非常适合处理批量操作,避免重复传参。
4. 总结
本文介绍了函数式编程的基本概念,并解释了 Scala 是如何体现函数式特性的,以及为什么值得学习。
Scala 同时具备面向对象和函数式编程的能力,让我们在熟悉的 OOP 场景中逐步接触 FP 思想,是一个不错的入门选择。
代码示例可以在 GitHub 上找到。