一、函数式编程的核心思想
函数式编程,说白了就是一种编程范式。它强调用函数来表达一切逻辑。我刚开始接触 Kotlin 时,其实不太理解为什么要搞这么复杂。直到我在一个数据流处理项目中,被各种状态变更搞得焦头烂额,才真正体会到函数式编程的威力。
嗯,咱们今天要聊的这几个概念——纯函数、不可变性、函数组合、柯里化、Monad——它们不是孤立的。它们是一套完整的思维体系。你想想看,如果每个函数都像数学公式一样可靠,那调试起来得多省心?
1.1 纯函数
纯函数有两个特点:
- 相同的输入,永远得到相同的输出
- 没有副作用(不修改外部变量、不打印、不写文件)
举个例子:
// 纯函数
fun add(a: Int, b: Int): Int = a + b
// 不纯的函数
var counter = 0
fun impureAdd(a: Int, b: Int): Int {
counter++ // 副作用:修改了外部变量
return a + b
}
我在项目中遇到过这样的坑:一个看似简单的计算函数,内部偷偷修改了全局缓存。结果多线程环境下,数据全乱套了。从那以后,我写工具函数时都会问自己一句:「这个函数纯不纯?」
saveToDb(),让调用者一眼就能看出来。
1.2 不可变性
不可变性,就是数据一旦创建,就不能再修改。Kotlin 用 val 和 var 来区分。
// 可变
var list = mutableListOf(1, 2, 3)
list.add(4) // 直接修改原列表
// 不可变
val list2 = listOf(1, 2, 3)
val newList = list2 + 4 // 返回新列表,原列表不变
你可能会问:「每次都创建新对象,性能会不会很差?」
嗯,这个问题我当年也纠结过。实际上,Kotlin 的集合操作做了很多优化。比如 + 操作符,如果只是添加一个元素,底层会复用大部分数据。而且,不可变数据在并发场景下,根本不需要加锁。这个收益,远远大于那点性能损耗。
copy() 创建新对象,问题就解决了。
二、函数组合与柯里化
2.1 函数组合
函数组合,就是把多个小函数拼成一个新函数。就像搭积木一样。
// 两个小函数
fun trim(s: String): String = s.trim()
fun toUpper(s: String): String = s.uppercase()
// 组合成一个新函数
val trimAndUpper: (String) -> String = { s -> toUpper(trim(s)) }
// 或者用扩展函数风格
infix fun <A, B, C> ((B) -> C).compose(f: (A) -> B): (A) -> C = { a -> this(f(a)) }
val composed = ::toUpper compose ::trim
println(composed(" hello ")) // 输出 "HELLO"
我个人习惯把复杂的业务逻辑拆成多个小函数,然后用组合的方式串联起来。这样每个小函数都可以单独测试,出了问题也容易定位。
2.2 柯里化
柯里化,就是把一个多参数的函数,拆成一系列单参数的函数。Kotlin 原生不支持自动柯里化,但我们可以手动实现。
// 普通函数
fun add(a: Int, b: Int, c: Int): Int = a + b + c
// 柯里化版本
fun curriedAdd(a: Int): (Int) -> (Int) -> Int = { b -> { c -> a + b + c } }
// 使用
val add5 = curriedAdd(5) // 固定第一个参数
val add5And3 = add5(3) // 固定第二个参数
val result = add5And3(2) // 输出 10
你想想看,柯里化的好处是什么?是「延迟求值」和「参数复用」。我在写配置类的时候经常用这个技巧。比如一个网络请求,先固定 baseUrl,再固定 token,最后只需要传具体接口参数就行了。
三、Monad 概念
Monad 这个词,听起来很高大上。其实说白了,它就是一种「包装类型」,帮你处理带有上下文的值。
Kotlin 中最常见的 Monad 就是 Nullable 和 Result。咱们拿 Nullable 举例:
// 没有 Monad 的时候
fun getUserName(userId: String): String? {
val user = findUser(userId) ?: return null
val profile = user.profile ?: return null
return profile.name
}
// 用 Monad 的 flatMap(Kotlin 里叫 let)
fun getUserNameMonad(userId: String): String? {
return findUser(userId)
?.let { it.profile }
?.let { it.name }
}
看到了吗?let 就是 Monad 的 flatMap 操作。它帮我们处理了「可能为空」这个上下文。如果中间某一步返回 null,后面的代码就不会执行了。
我在项目中用过 Arrow 库,它提供了更完整的 Monad 支持,比如 Either、Option、Try 等。不过说实话,对于大多数 Kotlin 项目,标准库自带的 let、run、also 已经够用了。
四、知识体系总览
下面这张图,帮你理清今天讲的所有概念之间的关系:
五、实战建议
说了这么多理论,咱们来点实际的。我在项目中总结了几条经验:
- 先从纯函数开始:写新函数时,先问自己「这个函数能不能写成纯函数?」能的话,就写成纯函数。
- 数据类用 val:Kotlin 的
data class默认属性都是val,这是有道理的。尽量别改成var。 - 用 let 代替 if-null:链式调用
?.let比写一堆if (x != null)要优雅得多。 - 不要过度设计:如果你的团队还不熟悉函数式编程,先引入纯函数和不可变性就够了。Monad 可以慢慢来。
一句话总结:函数式编程不是银弹,但它能帮你写出更可靠、更易测试的代码。从纯函数和不可变性开始,慢慢你就会发现,代码里的 bug 少了很多。