如何使用 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
值。
我正处于尝试设置一些数据然后调用服务的情况。每一步都可能失败,所以我尝试使用 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
值。