con*_*cid 2 functional-programming kotlin arrow-kt
在处理之前,我们经常需要一些请求验证。使用箭头 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)
}
Run Code Online (Sandbox Code Playgroud)
这似乎是对工作流的方便且富有表现力的定义。我试图在箭头 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
}
}
Run Code Online (Sandbox Code Playgroud)
这会导致编译器错误“只能在协程体内调用暂停函数”。原因是map和 的flatMap功能Either和Option都不是inline。
事实上,关于 fx的博客文章说
“很快你会发现你不能在为任何声明的函数中调用挂起函数,比如上面提到的那些,以及其他粉丝的最爱,比如 map() 和 handleErrorWith()。为此,你需要一个并发库!”
所以问题是为什么会这样,这种组合的惯用方式是什么?
惯用的方式是
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
})
Run Code Online (Sandbox Code Playgroud)
就我个人而言,我不会用那个去深入 IO,而是重写为挂起函数
suspend fun handleDownloadRequest(strUrl: String): Either<BadUrl, MyObject> =
parseUrl(strUrl)
.fold(::Left) { // forward the error
Either.catch({ /* Throwable to BadURL */ }) { downloadObject(it) }
}
Run Code Online (Sandbox Code Playgroud)
发生的事情是,在 0.8.X 中,Either用于内联的函数。一个意想不到的副作用是你可以在任何地方调用挂起函数。虽然这很好,但它可能导致在 amap或 a中间抛出异常(或跳转线程或死锁)flatMap,这对于正确性来说很糟糕。这是拐杖。
在 0.9(或者是 10?)中,我们移除了那个拐杖,并在 API 中将其明确化:Either.catch. 我们保持fold内联,因为它与 相同when,所以那里没有真正的正确性权衡。
因此,推荐的方法是在suspend任何地方使用,并且仅IO在尝试进行线程、并行、取消、重试和调度或任何真正高级的事情时使用。
对于基本用例suspend,Either.catch就足够了。要suspend在程序边缘或需要与这些高级行为桥接的地方调用函数,请使用IO.
如果您想继续使用Either,您可以自行承担风险定义常规函数的挂起/内联版本;或等到IO<E, A>0.11 中您可以使用effectEitherand effectMapEither。
| 归档时间: |
|
| 查看次数: |
518 次 |
| 最近记录: |