1. 概述

Scala 是一门类型安全的 JVM 编程语言,融合了面向对象和函数式编程的优点,语法简洁、表达力强。它不仅支持 Apache Spark 这样的大数据处理框架,还能根据需求灵活扩展程序规模。这些强大的特性让 Scala 成为一门实用且备受青睐的语言。

本教程将带你了解 使用 Scala 实现函数式编程的核心概念

2. 什么是函数式编程?

函数式编程是一种以 函数为核心构建模块 的编程范式。在函数式编程中,我们追求使用纯函数和不可变数据。

2.1. 不可变性(Immutability)

不可变性意味着编程时使用的是常量,也就是说变量的值或状态不能被修改。对于对象来说,我们可以创建一个新对象,但不能改变已有对象的状态。因此,不可变对象相比可变对象更加线程安全。我们会在后续章节中看到 Scala 是如何强制实现不可变性的。

2.2. 纯函数(Pure Functions)

纯函数有两个关键属性:

  1. 对于相同的输入,总是返回相同的结果;
  2. 没有副作用(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 标准库中的 mapfilterfold 等函数:

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() 使用了外部定义的变量 ratetime,这两个变量被称为自由变量。这就是闭包。

⚠️ 自由变量不是局部变量,也不是函数的形式参数。

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 上找到。


原始标题:Introduction to Functional Programming in Scala