kotlin函数式编程

函数式编程

kotlin的函数式编程推荐使用arrow库。

option

option其实与kotlin的可空修饰符作用有点重叠,当你返回一个option<T>,意味着返回T?,而且,option还有额外的小对象分配开销。不过,option可用map、flatMap等接口,可做函数式组合,与Either也能无缝结合。当然,T?也能使用take/let这样的接口,达到类似函数式组合的链式写法。

因此,在kotlin中,优先用T?,要与arrow库的其它数据结构结合使用时,再替换为option。在java中,特别是公共接口的返回值,则尽量用optional,这样可以明确的告知使用者,我的接口可能返回null,你需要用orElse避免NPE。

Either

Either结合flatMap或bind,可以替换掉传统的try-catch写法。调用层深比较深的情况下(比如spring场景),是可以降低抛异常退栈开销的。下面是例子:

kotlin 复制代码
object IOMonad {

    /* 1. 用 Effect 包装,返回一段程序声明,并不真正执行读文件的动作 */
    fun readFileEffect(path: String): Effect<Throwable, String> = effect {
        // 读文件会抛异常,这里用Either.catch截住,得到Either<Throwable, String>
        Either.catch { File(path).readText() }.bind()
    }

    /* 2. 组合业务:读文件  + 返回长度 */
    suspend fun fileLengthWorkflow(path: String): Either<String, Int> =
        either {
            val content = readFileEffect(path)        // Effect<Throwable, String>
                .toEither()                           // toEither导致effect真实执行,并返回Either
                .mapLeft { "读取失败: ${it}" } // 定制Left错误
                .bind() // 若出错,此处短路
            content.length // 若成功,走到此处
        }

    /* 3. 执行入口 */
    fun testEffect() {
        runBlocking {
            val result = fileLengthWorkflow("build.gradle.kts1")
            result.fold(
                { println("❌ $it") },
                { println("✅ 文件长度 = $it") }
            )
        }

    }
}

上例中,Either.catch把最底层异常转成Either,此后的链式操作借助Either.bind可以完全避免冗繁的check-return写法,只需关注正常流,一镜到底,既能享受异常体系带来的代码清爽的好处,又不必承担深层异常的调用栈开销。

Either.bind底层使用抛出异常的方式,造成短路效果。either和effect代码块会截住这个短路异常,并组装Either.Left返回。

Either.flatMap则不依赖异常就能造成短路,因为它只处理Either.Right,对于Either.Left,flatMap只是透传而已。

bind和flatMap两种方法谈不上孰优孰劣,取决于具体的使用场景。

函数组合和curry化

下面样例展示了组合和curry化的联合使用:

kotlin 复制代码
    val add: (Int, Int) -> Int = { x, y -> x + y }
    val mul: (Int, Int) -> Int = { x, y -> x * y }
    fun testComposeAndCurry() {
        val add5 = add.curried()(5)
        val mul2 = mul.curried()(2)
        val pipeline = add5 andThen mul2
        println(pipeline(7)) // 结果为24

    }

andThen和compose只能组装单参数函数,因此必须先做curry化,将add和mul转成单参数版本,才能组合。顺带说一下,在不支持函数为第一类对象的java语言中,curry化只能用lambda来模拟。

相关推荐
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
伞伞悦读2 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
这个DBA有点耶2 天前
MVCC深入:Read View、版本链与快照读——InnoDB并发控制的内核
数据库·mysql·架构
DBA_G2 天前
从地面到云霄:GBase数据库在民航三大场景的落地实践
数据库
自由能燃气设备2 天前
商用全预混低氮冷凝锅炉免费方案vs付费方案对比+选型避坑指南
大数据·数据库·人工智能
C语言小火车2 天前
C/C++ 为什么需要编译器?
开发语言·c++
科创致远2 天前
科创致远 ESOP 系统核心效能与实战价值展示
大数据·数据库·人工智能·精益工程
霍霍的袁2 天前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
孙启超2 天前
【AI开发之Rust】第 11 课:智能指针与内部可变性
开发语言·后端·rust
此生决int2 天前
深入理解C++系列(20)——C++11(下)
开发语言·c++