如何使用 Either monad 并避免嵌套的 flatMap

How to use Either monad and avoid nested flatMap

我正处于尝试设置一些数据然后调用服务的情况。每一步都可能失败,所以我尝试使用 Arrow 的 Either 来管理它。

但我最终得到了很多嵌套的平面地图。

以下代码片段说明了我正在尝试做的事情:

import arrow.core.Either
import arrow.core.flatMap

typealias ErrorResponse = String
typealias SuccessResponse = String

data class Foo(val userId: Int, val orderId: Int, val otherField: String)
data class User(val userId: Int, val username: String)
data class Order(val orderId: Int, val otherField: String)

interface MyService {
    fun doSomething(foo: Foo, user: User, order: Order): Either<ErrorResponse, SuccessResponse> {
        return Either.Right("ok")
    }
}

fun parseJson(raw: String): Either<ErrorResponse, Foo> = TODO()
fun lookupUser(userId: Int): Either<ErrorResponse, User> = TODO()
fun lookupOrder(orderId: Int): Either<ErrorResponse, Order> = TODO()

fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> {
    val foo = parseJson(rawData)
    val user = foo.flatMap {
        lookupUser(it.userId)
    }
    //I want to lookupOrder only when foo and lookupUser are successful
    val order = user.flatMap {
        foo.flatMap { lookupOrder(it.orderId) }
    }
    //Only when all 3 are successful, call the service
    return foo.flatMap { f ->
        user.flatMap { u ->
            order.flatMap { o ->
                myService.doSomething(f, u, o)
            }
        }
    }
}

我相信有更好的方法来做到这一点。有人可以用惯用的方法帮助我吗?

您可以使用 either { } DSL,这可以通过 suspend 方式或 non-suspend 方式通过 either.eager { }建设者。

这样你就可以使用 suspend fun <E, A> Either<E, A>.bind(): A.

重写您的代码示例:

fun start(rawData: String, myService: MyService): Either<ErrorResponse, SuccessResponse> =
  either.eager {
    val foo = parseJson(rawData).bind()
    val user =  lookupUser(foo.userId).bind()
    val order = lookupOrder(foo.orderId).bind()
    myService.doSomething(foo, user, order).bind()
  }

如果你运行变成Either.Left,那么bind()short-circuit阻塞either.eager并且return 与遇到的 Either.Left 值。