Kotlin 函数式编程:进阶篇

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Kotlin中的函数式编程:高级水平

Kotlin是一种多范式语言,它有机地结合了面向对象和函数式方法。如果您已经掌握了函数式编程(FP)的基本概念,例如map、filter、fold和lambda表达式,那么是时候深入高级技术了。在本文中,我们将讨论函数组合、柯里化、惰性求值,并通过Kotlin示例介绍函子和单子。所有代码示例都可以在您的IDE中运行。

1. 函数组合与柯里化

函数组合是通过依次应用两个或多个函数来创建新函数。Kotlin没有像Haskell那样的内置组合运算符,但可以通过扩展函数轻松实现。

函数组合示例

// 定义函数
val double: (Int) -> Int = { it * 2 }
val increment: (Int) -> Int = { it + 1 }

// 组合:先double,后increment
infix fun <A, B, C> ((B) -> C).compose(f: (A) -> B): (A) -> C = { a -> this(f(a)) }

val doubleThenIncrement = increment compose double
println(doubleThenIncrement(5)) // 11 (5*2 + 1)

柯里化(Currying)

柯里化是将一个多参数函数转换为一系列单参数函数的过程。在Kotlin中,这通过嵌套的lambda表达式实现。

// 普通函数
fun sum(a: Int, b: Int): Int = a + b

// 柯里化版本
fun curriedSum(a: Int): (Int) -> Int = { b -> a + b }

val add5 = curriedSum(5)
println(add5(3)) // 8

// 通用的柯里化函数
fun <A, B, C> ((A, B) -> C).curry(): (A) -> (B) -> C = { a -> { b -> this(a, b) } }

val curriedSum2 = ::sum.curry()
println(curriedSum2(10)(20)) // 30

柯里化在处理部分应用和函数工厂时特别有用。

2. 函子与单子:理论与实践

函子是一个可以使用函数进行映射(map)的容器。单子是一个支持容器组合(flatMap)的函子。在Kotlin中,Result(或Either)以及Sequence是单子的典型例子。

使用Result的示例(Either单子)

data class User(val name: String, val email: String)

fun parseUser(json: String): Result<User> = runCatching {
    // 模拟解析
    if (json.isBlank()) throw IllegalArgumentException("Empty JSON")
    User("Alice", "alice@example.com")
}

fun sendEmail(user: User): Result<String> = runCatching {
    if (user.email.contains("@")) "Email sent to ${user.email}"
    else throw IllegalArgumentException("Invalid email")
}

// 单子链式调用
val result = parseUser("{...}")
    .flatMap { user -> sendEmail(user) }
    .map { message -> "Success: $message" }
    .onFailure { error -> println("Error: ${error.message}") }

println(result.getOrDefault("Failed"))

这里的flatMap避免了嵌套的成功/错误检查,使代码线性且可读。

创建自己的函子:Option类型

sealed class Option<out T> {
    data class Some<T>(val value: T) : Option<T>()
    object None : Option<Nothing>()
}

fun <T, R> Option<T>.map(f: (T) -> R): Option<R> = when (this) {
    is Option.Some -> Option.Some(f(value))
    is Option.None -> Option.None
}

fun <T, R> Option<T>.flatMap(f: (T) -> Option<R>): Option<R> = when (this) {
    is Option.Some -> f(value)
    is Option.None -> Option.None
}

val maybeNumber: Option<Int> = Option.Some(42)
val resultOption = maybeNumber
    .map { it * 2 }
    .flatMap { Option.Some("Value: $it") }

println(resultOption) // Some(value=Value: 84)

博客

书籍推荐