Scala 函数柯里化及闭包
一、柯里化
1.1 定义
柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数的过程。每个函数返回一个新函数,直到所有参数都被收集完毕,最终返回结果。
1.2 示例
非柯里化函数(普通多参数函数)
def add(a: Int, b: Int): Int = a + b
add(2, 3) //输出5
柯里化函数(多参数列表)
def addCurried(a: Int)(b: Int): Int = a + b
addCurried(2)(3) //输出5
1.3 使用柯里化的意义
Scala 使用柯里化风格可以简化主函数的复杂度,提高主函数的自闭性,提高功能上的可扩张性。
1.4 柯里化的作用
- 部分应用函数
可以固定部分参数,生成新函数:val addTwo: Int => Int = addCurried(2) // 固定第一个参数为2 addTwo(3) //输出5
- 延迟计算
分阶段传递参数,适合需要动态配置的场景:def connect(url: String)(timeout: Int): Unit = {println(s"连接到 $url,超时时间 $timeout ms") }val connectToDB = connect("jdbc:mysql://localhost:3306/mydb") _ connectToDB(5000) //输出:连接到 jdbc:mysql://localhost:3306/mydb,超时时间 5000 ms
- 类型推断优化
Scala 的类型推断按参数列表从左到右工作,柯里化可帮助编译器推断类型:def foldLeft[T](list: List[T])(initial: T)(op: (T, T) => T): T = list.foldLeft(initial)(op)// 编译器能推断出 op 的参数类型为 (Int, Int) => Int foldLeft(List(1, 2, 3))(0)(_ + _) //输出6
- 柯里化与高阶函数
柯里化常用于需要传递函数参数的场景:def processData(data: List[Int])(filter: Int => Boolean)(mapper: Int => String): List[String] = {data.filter(filter).map(mapper) }val result = processData(List(1, 2, 3, 4))(_ % 2 == 0)(_.toString) // 输出List("2", "4")
1.5 柯里化的实际应用场景
- 依赖注入
class Database(config: Config) {/* ... */} class Service(db: Database) {/* ... */}def createService(config: Config)(dbFactory: Config => Database): Service = {val db = dbFactory(config)new Service(db) }// 使用柯里化分阶段传递依赖 val service = createService(config)(new Database(_))
- DSL 设计
// 定义一个柯里化的 HTTP 请求构建器 def httpRequest(method: String)(url: String)(headers: Map[String, String]): Unit = {println(s"$method %url Headers: $headers") }// 链式调用更清晰 httpRequest("GET")("https://api.example.com")(Map("Authorization" -> "Bearer token"))
1.6 注意事项
- 性能:柯里化会生成多个中间函数,可能对性能敏感场景有影响。
- 可读性:过度使用柯里化可能降低代码可读性。
- 隐式参数:柯里化常用于结合饮食参数(饮食参数必须在最后一个参数列表):
def process(input: String)(implicit ec: ExecutionContext): Unit = {// 使用隐式 ExecutionContext }
二、闭包
2.1 定义
闭包(Closure)是能够捕获并持有其外部作用域中的变量(自由变量)的函数,即使该外部作用域已经执行完毕。
2.2 闭包实现原理
- Scala 编译器会为闭包生成匿名类,捕获的变量作为类的成员字段。
- 每个闭包实例独立维护其捕获的变量的副本。
反编译后的 Java 代码结构:
// 近似等价于以下实现
class ClosureExample$AnonClass {private int factor;public ClosureExample$AnonClass(int factor) { this.factor = factor; }public int apply(int x) { return x * factor; }
}
2.3 闭包的核心特征
- 跨作用域的生命周期:闭包可以访问定义时所在作用域的变量。
- 动态绑定:闭包内存引用的变量值在闭包被调用时确定,而非定义时。
- 匿名函数必然存在闭包
2.4 闭包的作用
Scala 闭包通过捕获外部作用域的变量,实现了上下文保持和状态封装,是函数式编程的重要特性。合理使用闭包可以:
- 创建有状态的函数对象
- 实现延迟计算和回调机制
- 构建灵活的抽象接口
2.5 代码示例
def outter(a: Int) = {def inner(b: Int) {a + b}inner _
}outter(10)(20) // 输出 30
2.6 闭包的关键特性
- 捕获外部变量
var counter = 0 val increment: () => Int = () => {counter += 1 //捕获并修改外部变量counter }
- 动态绑定
def createGreeter(greeting: String): String => String = {(name: String) => s"$greeting, $name" //greeting 在闭包创建时绑定 }val sayHello =createGreeter("Hello") val sayHi =createGreeter("Hi")println(sayHello("Alice")) // Hello, Alice println(sayHi("Bob")) // Hi, Bob
- 延迟执行
def delayExecution(msg: String): () => Unit = {() => println(s"Delayed: $msg") // msg 在闭包创建时捕获,但在输出延迟到调用时 }val delayed = delayExecution("Hello World") // ....其他代码 delayed() // 输出"Delayed: Hello World"
2.7 实际应用场景
- 状态保持
def makeCounter(): () => Int = {var count = 0() => { count += 1; count } // 闭包保持 count 状态 }val counter = makeCounter() println(counter()) // 1 println(counter()) // 2
- 配置化行为
def createFilter(predicate: Int => Boolean): List[Int] => List[Int] = { list => list.filter(predicate) // 闭包携带 predicate 配置 }val filterEven = createFilter(_ % 2 == 0 ) println(filterEven(List[1, 2, 3, 4])) // List(2,4)
- 回调函数
def onEvent(eventName: String)(callback: String => Unit): Unit = {//模拟事件发生if(eventName == "click") callback("Button clicked") }onEvent("click") { message => println(s"Received event: $message") // 闭包捕获了上下文 }
2.8 注意事项
- 避免意外延长变量生命周期导致内存泄漏
def createClosure(): () => Unit = {val resource =acquireResource() //资源被闭包持有,可能导致内存泄漏() => { use(resource); release(resource) } }
- 谨慎处理并发环境下的共享状态(可变状态风险)
var shared = 0 val closures = (1 to 5).map(_ => () => { shared += 1 }) closures.foreach(_.apply()) println(shared) // 输出 5 (但并发时会有线程安全问题)
- 在循环中正确绑定变量值
val functions = new Array[() => Int](3) for (i <- 0 to 2) {functions(i) = () => i // 所有闭包共享最终 i 的值(输出全为3) } functions.foreach(f => println(f())) // 输出 3, 3, 3// 正确做法:使用局部变量拷贝 for (i <- 0 to 2) {val current = ifunctions(i) = () => current // 输出 0,1,2 }