1

我对返回 Either<Exception, Something> 的外部系统有两个异步函数调用,需要合并它们的结果。作为 Arrow-Kt 函数式编程的初学者,我想知道哪种方法是完成这项任务的最佳方式。下面是我目前正在使用的代码。它确实有效,但并不真正“感觉”最直接。我正在寻找一种更“实用”的风格来获得结果。注意:成功列表结果的前期使用是必要的。

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 */ }
}
4

2 回答 2

6

最简单的方法是either { }parZip. either { }允许您从中提取A,Either<E, 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()摘自. A_ Either<E, A>我们在内部执行此操作,parZip这样每当Left遇到 a 时,它都会使块短路either { },这样做还会取消parZip.

这样如果getAs()立即返回,Left则它成为的输出值either { }并被getBs()取消。

于 2021-06-08T07:10:07.770 回答
3

我正要发布一个非常相似的答案。请注意,getAsandgetBs 并不是真正的顺序,因为getBs不需要getAs执行结果。他们只碰巧需要最终组合结果。换句话说:我们可以并行化

在西蒙建议的基础上,您还有一些我想做的事情。(我将在本例中替换 A 和 B,NetworkUserDbUser尝试赋予它一些语义,否则过滤器上的那些“id”属性将不起作用。

捕获错误并将它们映射到每个有效函数上的强类型域错误。

这将有助于减轻程序其余部分的负担,并在它之上提供更安全的域错误层次结构,我们可以在需要时对其进行详尽的评估。

suspend fun <A> getUsersFromNetwork(): Either<DomainError, List<NetworkUser>> =
 Either.catch { fetchUsers() }
   .mapLeft { exception ->
     println("special message on error for A")
     exception.toDomain()
   }

让 doSomething 函数返回 Either,以防万一它也可能失败。

这是您在初始获取后立即需要的功能,这意味着 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()
networkUsers
于 2021-06-08T07:30:53.853 回答