2

在处理它之前,我们经常需要一些请求验证。使用箭头 v 0.8,典型的消息处理程序如下所示:

fun addToShoppingCart(request: AddToShoppingCartRequest): IO<Either<ShoppingCardError, ItemAddedEvent>> = fx {
    request
    .pipe (::validateShoppingCard)
    .flatMap { validatedRequest ->
        queryShoppingCart().bind().map { validatedRequest to it }         // fun queryShoppingCart(): IO<Either<DatabaseError, ShoppingCart>>
    }
    .flatMap { (validatedRequest, shoppingCart) ->
        maybeAddToShoppingCart(shoppingCart, validatedRequest)            // fun maybeAddToShoppingCart(...): Either<DomainError, ShoppingCart>
    }
    .flatMap { updatedShoppingCart ->
        storeShoppingCart(updatedShoppingCart).bind()                     // fun storeShoppingCart(ShoppingCart): IO<Either<DatabaseError, Unit>>
        .map {
            computeItemAddedEvent(updatedShoppingCart)
        }
    }
    .mapLeft(::computeShoppingCartError)
}

这似乎是工作流的一个方便且富有表现力的定义。我试图在箭头 v 0.10.5 中定义类似的功能:

fun handleDownloadRequest(strUrl: String): IO<Either<BadUrl, MyObject>> = IO.fx {
    parseUrl(strUrl)                                                      // fun(String): Either<BadUrl,Url>
    .map {
        !effect{ downloadObject(it) }                                     // suspended fun downloadObject(Url): MyObject
    }
}

这会导致编译器错误“只能在协程主体内调用暂停函数”。原因是既mapflatMap功能EitherOption不是inline

事实上,关于 fx 的博客文章

“很快你会发现你不能在为 Either 声明的函数中调用挂起函数,例如上面提到的函数,以及其他粉丝喜欢的 map() 和 handleErrorWith()。为此,你需要一个并发库!”

所以问题是为什么会这样,这种组合的惯用方式是什么?

4

1 回答 1

7

惯用的方法是

fun handleDownloadRequest(strUrl: String): IO<Either<BadUrl, MyObject>> =
    parseUrl(strUrl)
      .fold({
        IO.just(it.left())  // forward the error
      }, {
        IO { downloadObject(it) }
          .attempt() // get an Either<Throwable, MyObject>
          .map { it.mapLeft { /* Throwable to BadURL */ } } // fix the left side
      })

就我个人而言,我不会用那个去深入 IO,而是重写为挂起函数

suspend fun handleDownloadRequest(strUrl: String): Either<BadUrl, MyObject> =
    parseUrl(strUrl)
      .fold(::Left) { // forward the error
        Either.catch({ /* Throwable to BadURL */ }) { downloadObject(it) }
      }

发生的事情是,在 0.8.X 中,Either用于内联的函数。一个意想不到的副作用是您可以在任何地方调用挂起函数。虽然这很好,但它可能导致在 amap或 a中间抛出异常(或跳转线程或死锁) flatMap,这对于正确性来说是很糟糕的。这是一个拐杖。

在 0.9(或者是 10?)中,我们移除了那个拐杖,并在 API 中将其变成了明确的东西:Either.catch. 我们保持fold为内联,因为它与 相同when,所以那里没有真正的正确性权衡。

因此,推荐的做法是在suspend任何地方使用,并且仅IO在尝试执行线程、并行、取消、重试和调度或任何真正高级的事情时才使用。

对于基本用例suspendEither.catch就足够了。要suspend在程序边缘或需要桥接这些高级行为的地方调用函数,请使用IO.

如果您想继续使用 Either,您可以定义常规函数的挂起/内联版本,风险自负;或等到IO<E, A>0.11 中您可以使用effectEitherand effectMapEither.

于 2020-05-14T20:34:00.510 回答