scala的函数进阶

36 阅读6分钟

你想要深入掌握 Scala 函数的进阶特性,这些特性是 Scala 函数式编程的核心,也是其区别于传统命令式语言的关键。下面将从高阶函数、匿名函数、闭包、柯里化、偏应用函数等核心知识点展开,结合实战示例帮你全面理解。

一、高阶函数(Higher-Order Function)

1. 核心定义

高阶函数是 Scala 函数式编程的基础,满足以下两个条件之一即可:

  • 接收一个或多个函数作为参数
  • 返回值是一个函数它允许我们将函数作为「一等公民」传递和返回,实现逻辑的灵活复用。

2. 实战示例

示例 1:接收函数作为参数

// 定义高阶函数:接收两个Int和一个(Int, Int)=>Int类型的函数参数
def operate(a: Int, b: Int, op: (Int, Int) => Int): Int = op(a, b)

// 调用时传递匿名函数(或已有函数)
val sum = operate(3, 5, (x, y) => x + y)       // 求和,输出:8
val product = operate(4, 6, (x, y) => x * y)   // 求积,输出:24
val maxVal = operate(10, 20, math.max)         // 传递已有函数,输出:20

示例 2:返回一个函数

// 定义高阶函数:返回一个(Int)=>Int类型的函数
def createMultiplier(factor: Int): Int => Int = {
  (num: Int) => num * factor  // 返回匿名函数
}

// 调用:先获取函数,再执行函数
val double = createMultiplier(2)  // 获取「翻倍」函数
val triple = createMultiplier(3)  // 获取「三倍」函数
println(double(10))               // 输出:20
println(triple(10))               // 输出:30

二、匿名函数(Anonymous Function)

1. 核心定义

匿名函数是没有函数名的函数,也称为 lambda 表达式,常用于作为高阶函数的参数(无需单独定义命名函数,简化代码)。

2. 语法形式

  • 完整形式:(参数列表: 参数类型) => 表达式
  • 简化形式:省略参数类型(Scala 自动推导)、单个参数省略括号、用 _ 作为参数占位符(参数仅出现一次时)

3. 实战示例

// 1. 完整形式
val add1: (Int, Int) => Int = (x: Int, y: Int) => x + y

// 2. 简化形式1:省略参数类型(自动推导)
val add2 = (x, y) => x + y

// 3. 简化形式2:单个参数省略括号
val square = x => x * x  // 等价于 (x) => x * x

// 4. 简化形式3:下划线占位符(参数仅出现一次)
val add3: (Int, Int) => Int = _ + _  // 等价于 (x, y) => x + y
val multiply: (Int, Int) => Int = _ * _  // 等价于 (x, y) => x * y

// 作为高阶函数参数(最常用场景)
val nums = List(1, 2, 3, 4)
val doubled = nums.map(_ * 2)  // map是高阶函数,_*2是匿名函数简写,输出:List(2,4,6,8)

三、闭包(Closure)

1. 核心定义

闭包是一个可以捕获其外部作用域变量的函数(包括匿名函数和命名函数)。简单来说,函数定义时引用了函数外部的变量,当函数被传递或返回时,会携带这个外部变量的上下文,形成「闭合」的环境。

2. 关键特性

  • 闭包可以访问并修改其外部作用域的变量(即使外部作用域已经执行完毕)
  • 外部变量的生命周期会随闭包延长,而非随外部作用域销毁

3. 实战示例

def createCounter(): () => Int = {
  var count = 0  // 外部变量(函数作用域内,非函数参数)
  // 匿名函数引用了外部变量count,形成闭包
  () => {
    count += 1  // 修改外部变量
    count       // 返回当前计数
  }
}

// 创建两个计数器(各自持有独立的count变量)
val counter1 = createCounter()
val counter2 = createCounter()

println(counter1())  // 输出:1
println(counter1())  // 输出:2
println(counter2())  // 输出:1(counter2的count独立,不受counter1影响)

四、函数柯里化(Currying)

1. 核心定义

柯里化是将一个接收多个参数的函数,转换为一系列接收单个参数的函数的过程。例如:将 (a: Int, b: Int) => Int 转换为 a: Int => (b: Int => Int),调用时可分步传递参数。

2. 语法形式

  • 定义:def 函数名(参数1: 类型1)(参数2: 类型2)...: 返回类型 = 表达式
  • 调用:可分步传递参数(函数名(参数1)(参数2)),也可部分传递参数(形成偏应用函数)

3. 实战示例

示例 1:基本柯里化函数

// 普通多参数函数
def addNormal(a: Int, b: Int): Int = a + b

// 柯里化函数(将两个参数拆分为两个单参数列表)
def addCurried(a: Int)(b: Int): Int = a + b

// 调用方式1:分步传递参数(推荐,可读性高)
val sum1 = addCurried(3)(5)  // 输出:8

// 调用方式2:一次性传递参数(与普通函数类似)
val sum2 = addCurried(4)(6)  // 输出:10

示例 2:柯里化的优势(分步复用逻辑)

// 柯里化函数:先传递「前缀」,再传递「内容」
def greet(prefix: String)(name: String): String = s"$prefix, $name!"

// 分步复用:先固定前缀,得到专用问候函数
val sayHello = greet("Hello")  // 固定前缀为Hello,返回 (String)=>String 函数
val sayHi = greet("Hi")        // 固定前缀为Hi,返回 (String)=>String 函数

// 后续直接调用专用函数
println(sayHello("Scala"))  // 输出:Hello, Scala!
println(sayHi("Java"))      // 输出:Hi, Java!

示例 3:普通函数转柯里化函数

// 普通函数
def multiplyNormal(a: Int, b: Int): Int = a * b

// 转为柯里化函数(使用 curried 方法)
val multiplyCurried = (multiplyNormal _).curried

// 调用
val double = multiplyCurried(2)  // 固定第一个参数为2
println(double(10))              // 输出:20

五、偏应用函数(Partially Applied Function)

1. 核心定义

偏应用函数是对一个多参数函数,预先传递部分参数,剩余参数在后续调用时补充的函数。它可以简化重复传递相同参数的场景,提升代码复用性。

2. 语法形式

  • 方式 1:使用下划线 _ 表示未传递的参数
  • 方式 2:柯里化函数分步传递参数(本质也是偏应用)

3. 实战示例

// 1. 普通多参数函数的偏应用
def log(level: String, time: String, message: String): Unit = {
  println(s"[$level] [$time] $message")
}

// 预先传递 level 和 time 参数,剩余 message 参数后续补充
val infoLog = log("INFO", "2025-12-29", _: String)
val errorLog = log("ERROR", "2025-12-29", _: String)

// 后续调用仅需传递 message
infoLog("System started successfully")  // 输出:[INFO] [2025-12-29] System started successfully
errorLog("Database connection failed")  // 输出:[ERROR] [2025-12-29] Database connection failed

// 2. 柯里化函数的偏应用(更简洁)
def logCurried(level: String)(time: String)(message: String): Unit = {
  println(s"[$level] [$time] $message")
}

// 预先传递 level 参数
val warnLog = logCurried("WARN")("2025-12-29")
warnLog("Low memory warning")  // 输出:[WARN] [2025-12-29] Low memory warning

六、函数的特殊形式

1. 懒加载函数(惰性函数)

使用 lazy 关键字修饰函数调用(或变量),表示延迟执行,直到第一次被使用时才会计算结果,且只计算一次。

lazy val lazyValue = {
  println("正在计算惰性值...")
  10 + 20
}

println("惰性值定义完成,尚未计算")
println(s"惰性值:$lazyValue")  // 第一次使用,执行计算并输出
println(s"惰性值:$lazyValue")  // 直接使用缓存结果,不再重复计算

2. 嵌套函数(内部函数)

在函数内部定义函数,用于封装辅助逻辑,对外隐藏实现细节,仅暴露主函数接口(常用于尾递归实现)。

import scala.annotation.tailrec

def factorial(n: Int): Int = {
  // 嵌套辅助函数(尾递归)
  @tailrec
  def loop(current: Int, acc: Int): Int = {
    if (current <= 1) acc
    else loop(current - 1, current * acc)
  }

  loop(n, 1)
}

println(factorial(5))  // 输出:120

总结

  1. 高阶函数:接收 / 返回函数的函数,是函数式编程的基础,实现逻辑灵活复用。
  2. 匿名函数:无名称的 lambda 表达式,简化高阶函数参数传递,支持多种简写形式。
  3. 闭包:捕获外部作用域变量的函数,延长外部变量生命周期,实现状态持有。
  4. 柯里化:将多参数函数转为单参数函数序列,支持分步传参和逻辑复用。
  5. 偏应用函数:预先传递部分参数,剩余参数后续补充,简化重复传参场景。
  6. 辅助特性:惰性函数(延迟执行)、嵌套函数(隐藏实现细节)进一步提升 Scala 函数的灵活性和可读性。