如何在Scala中链式使用Future[\/[A,B]]?

9

我该如何使用类型为Future[\/[String,Int]]的数据进行for推导呢?以下是一个起点,但无法编译。

import scala.concurrent.{ExecutionContext,future,Future}
import scalaz._
import Scalaz._
import ExecutionContext.Implicits.global

def calculateStuff(i:Int):Future[\/[String,Int]] = future{\/-(i)}

for {
   v1Either <- calculateStuff(1)
   v1Int <- v1Either
   v2Either < calculateStuff(v1Int)
   v2Int <- v2Either
   v3Either <- calculateStuff(v2Int)
   v3Int <- v3Either
} yield {
   v1Int + v2Int + v3Int
}

注意:calculateStuff 只是一个例子,实际上会有不同的函数,每个函数都依赖于前面的结果。
1个回答

17

我首先要说明的是,我假定你有足够的理由实现自己的错误处理(通过\/),而不是使用内置在Future中的功能。

如果是这种情况,正如你的标签所示,这种问题正是单子变换器的用处所在——只需在计算中包装一个EitherT即可:

import scalaz._, Scalaz._, contrib.std._
import scala.concurrent.{ ExecutionContext, future, Future }
import ExecutionContext.Implicits.global

def calculateStuff(i: Int): EitherT[Future, String, Int] =
  EitherT(future(\/-(i)))

val computation = for {
   v1Int <- calculateStuff(1)
   v2Int <- calculateStuff(v1Int + 1)
   v3Int <- calculateStuff(v2Int + 2)
} yield v1Int + v2Int + v3Int

请注意,我正在使用 Typelevelscalaz-contrib 库中的 FutureMonad 实例。
现在,computation.run 将给出一个 Future[String \/ Int]
如果您需要将纯值注入到计算中,只需使用 point 和类型 lambda 即可。
v4Int <- 1.point[({ type L[x] = EitherT[Future, String, x] })#L]

你可以定义自己的类型别名,使其看起来更加美观。
如果你想在for-comprehension中使用\/值,只需将其指向Future并将整个内容包装在EitherT中即可。
v5Int <- EitherT(1.right[String].point[Future])

你可以使用(名称有点令人困惑的)liftM将普通的Future提升到转换后的单子中:

v6Int <- future(1).liftM[({ type T[m[+_], a] = EitherT[m, String, a] })#T]

在这种情况下,您几乎肯定需要使用类型别名——那行代码大部分都是噪音。

1
哇,这正是我一直在寻找的,但我无法让它工作。你的假设是正确的,至少现在是这样。感谢你这个非常棒的答案! - Johnny Everson
如何在函数中混合结果为纯文本的“/”? - Johnny Everson
谢谢!我曾经担心需要添加很多管道或重构代码库才能开始使用你的解决方案。但是通过你最近的示例,即使只在我正在处理的函数上下文中使用,也可以使用它。再次感谢! - Johnny Everson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接