我有两个对外部系统的异步函数调用,返回Either,并且需要组合它们的结果。作为Arrow函数式编程的初学者,我想知道完成这一任务的最佳方法是什么。下面是我目前正在使用的代码。它当然有效,但并不真正“感觉”最直截了当。我正在寻找一个更“功能”的风格,以获得结果。注意:成功列表结果的前期使用是必要的。
suspend fun getAs(): Either<Exception, List<A>> = TODO()
suspend fun getBs(): Either<Exception, List<B>> = TODO()
suspend fun doSomethingWithA(listA: List<A>): Unit = TODO()
launch {
val deferredA = async { getAs() }
val deferredB = async { getBs() }
either<Exception, List<A>> {
val listOfAs = deferredA.await()
.bimap(leftOperation = { e ->
println("special message on error for A")
e
}, rightOperation = { listA ->
doSomethingWithA(listA)
listA
})
.bind()
val listOfBs = deferredB.await().bind()
listOfAs.filter { it.someId !in listOfBs.map { it.someProperty } }
}
.map { /* handle result */ }
.handleError { /* handle error */ }
}另一个选择是只使用map{}函数,如下所示
launch {
val deferredA = async { getAs() }
val deferredB = async { getBs() }
deferredA.await()
.bimap(leftOperation = { e ->
println("special message on error for A")
e
}, rightOperation = { listA ->
doSomethingWithA(listA)
deferredB.await().map { listB ->
listA.filter { a -> a.someId !in listB.map { it.someProperty } }
}
})
.map { /* handle result */ }
.handleError { /* handle error */ }
}发布于 2021-06-08 07:10:07
最简单的方法是将either { }和parZip结合起来。either { }允许您从Either<E, A>中提取A,parZip是一个用于并行运行suspend函数的实用函数。
suspend fun getAs(): Either<Exception, List<A>> = TODO()
suspend fun getBs(): Either<Exception, List<B>> = TODO()
suspend fun doSomethingWithA(listA: List<A>): Unit = TODO()
either {
val list = parZip(
{
getAs()
.mapLeft { e -> println("special message on error for A"); e }
.bind()
},
{ getBs().bind() },
{ aas, bbs ->
aas.filter { a -> a.someId !in bbs.map { it.someProperty }
}
)
/* Work with list and return value to `either { } */
}.handleError { /* handle error */ }这里,bind()从Either<E, A>中提取A。我们在parZip中这样做,这样每当一个Left遇到它时,它就会短路either { }块,通过这样做,它还会取消parZip中仍在运行的任务。
这样,如果getAs()立即与Left一起返回,那么它将成为either { }的输出值,而getBs()则被取消。
发布于 2021-06-08 07:30:53
我正要发布一个非常相似的答案。注意,getAs和getBs 并不是真正的顺序,因为getBs不需要执行getAs的结果。他们最终只需要把结果结合起来。换句话说:我们可以并行化
在西蒙建议的基础上,我还要做一些额外的事情。(在本例中,我将用NetworkUser和DbUser替换A和B,以尝试给出一些语义,因为否则过滤器上的"id“属性将无法工作。
捕获错误并将它们映射到在每个有效函数上强键入域错误。
这将有助于消除程序其余部分的负担,并在此基础上提供一个更安全的域错误层次结构,我们可以在需要时对其执行详尽的评估。
suspend fun <A> getUsersFromNetwork(): Either<DomainError, List<NetworkUser>> =
Either.catch { fetchUsers() }
.mapLeft { exception ->
println("special message on error for A")
exception.toDomain()
}使doSomething函数返回,以防它也会失败。
这是您说过的一个函数,它是在初始get之后需要的,这意味着flatMap或bind (它们是等价的)。如果我们将其提升到Either中,这将确保错误短路发生在预期的情况下,因此这个操作从未在最初的操作中运行过,因此没有成功。
我建议这样做,因为我怀疑这里的这个操作也是第一个操作的结果,可能是将第一个操作的结果存储在本地缓存中的结果,或者是简单地消耗该结果的另一种效果。
suspend fun doSomethingWithNetworkUsers(listA: List<NetworkUser>): Either<DomainError, Unit> = TODO()因此,我们将依赖于组合函数的功能如下所示:
suspend fun getUsersFromNetwork(): Either<DomainError, List<NetworkUser>> = TODO()
suspend fun getUsersFromDb(): Either<DomainError, List<DbUser>> = TODO()
suspend fun doSomethingWithNetworkUsers(listA: List<NetworkUser>): Either<DomainError, Unit> = TODO()而该方案:
fun CoroutineScope.program() {
launch {
either {
parZip(
{
val networkUsers = getUsersFromNetwork().bind()
doSomethingWithNetworkUsers(networkUsers).bind()
networkUsers
},
{ getUsersFromDb().bind() }
) { networkUsers, dbUsers ->
networkUsers.filter { networkUser ->
networkUser.id !in dbUsers.map { dbUser -> dbUser.id }
}
}
}
.map { /* do something with the overall result */ }
.handleError { /* can recover from errors here */ }
// Alternatively:
// .fold(ifLeft = {}, ifRight = {}) for handling both sides.
}
}通过将第一个操作作为一个组合的操作,首先绑定,如下面从上面提取的代码片段中所示,我们确保这两个操作都是在parZip lambda进行组合结果之前完成的。
val networkUsers = getUsersFromNetwork().bind()
doSomethingWithNetworkUsers(networkUsers).bind()
networkUsershttps://stackoverflow.com/questions/67882361
复制相似问题