You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优雅调用带多上下文接收器的Arrow.kt函数(替代Either)

优雅处理Arrow.kt多错误上下文接收器函数的调用

背景

Arrow.kt文档指出,我们可以用带上下文接收器的写法替代返回Either的函数:

原写法:

fun get(): Either<Err, Res>

替代写法:

context(Raise<Err>)
fun get(): Res

该写法支持多个上下文接收器。比如定义两种错误类型:

class Err1
class Err2

即可写出同时携带两种错误上下文的函数:

context(Raise<Err1>, Raise<Err2>)
private fun func(a: Int): Int {
    return when(a) {
        0 -> raise(Err1())
        1 -> raise(Err2())
        else -> a
    }
}

核心问题

如何优雅调用该函数,无需深层嵌套就能处理部分或全部错误?目前常见的嵌套写法十分繁琐:

recover<Err1, Unit>({
    recover<Err2, Unit>({
        val res = func(1)
        // 正常处理逻辑
    }) { err2 -> /* 处理Err2 */ }
}){ err1 -> /* 处理Err1 */ }

拆分函数虽能消除嵌套,但会让代码更零散,得不偿失。

优雅解决方案

方案1:链式调用recover

Arrow支持将recover操作链式调用,把多错误处理逻辑平铺开来:

runCatchingRaises {
    val res = func(1)
    // 正常处理逻辑
}
.recover { err: Err1 ->
    // 处理Err1
}
.recover { err: Err2 ->
    // 处理Err2
}

这里的runCatchingRaises可使用Arrow提供的Either.catch,或自定义适配多Raise上下文的启动器,本质是将多错误上下文转换为可链式处理的错误流。

方案2:用密封类+fold统一处理

先将函数调用结果转换回Either类型,通过密封类统一错误类型,再用fold完成所有逻辑处理:

首先定义密封错误类:

sealed class AppErr {
    data class WrappedErr1(val err: Err1) : AppErr()
    data class WrappedErr2(val err: Err2) : AppErr()
}

然后转换并处理:

val result = Either.catch<AppErr, Int> {
    func(1)
}
.fold(
    ifLeft = { err ->
        when(err) {
            is AppErr.WrappedErr1 -> /* 处理Err1 */
            is AppErr.WrappedErr2 -> /* 处理Err2 */
        }
    },
    ifRight = { res -> /* 正常处理逻辑 */ }
)

这种方式通过密封类收拢错误类型,用一个fold就能完成所有错误与正常逻辑的处理,完全避免嵌套。

方案3:封装扩展函数平铺处理逻辑

自定义扩展函数,将多错误处理逻辑作为参数传入,实现平铺式调用:

context(Raise<Err1>, Raise<Err2>)
fun <R> handleErrors(
    onErr1: (Err1) -> R,
    onErr2: (Err2) -> R,
    block: context(Raise<Err1>, Raise<Err2>) () -> R
): R {
    return try {
        block()
    } catch (e: Err1) {
        onErr1(e)
    } catch (e: Err2) {
        onErr2(e)
    }
}

调用时:

handleErrors(
    onErr1 = { /* 处理Err1 */ },
    onErr2 = { /* 处理Err2 */ }
) {
    val res = func(1)
    // 正常处理逻辑
}

此方式将错误处理逻辑作为参数平铺,代码结构更清晰直观。

附言

带上下文接收器的Arrow.kt写法确实让人联想到Java的受检异常,区别在于这里的错误无需继承Throwable,且错误处理更灵活简洁。有意思的是,我们从Java的受检异常,到Kotlin取消受检异常,再到Arrow.kt用上下文接收器模拟类似受检异常的机制,像是绕了一圈又回到了某种形式的显式错误声明。

内容的提问来源于stack exchange,提问作者Alevinevich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:42:25