我认为主要问题是在Future[Either[Error, X]] 上的flatMap 在EitherT[Future, Error, X]-monad 堆栈的意义上很尴尬,因为Future 的原始flatMap 妨碍了,并且编译器不是在寻找可以同时处理Future 和Either 组合的monad 实例。但是,如果您将期货包装在 EitherT 中,一切都会顺利进行。
适用于猫 1.0.1
在下文中,(a,b).tupled 对应于Cartesian.product(a, b),(a, b).mapN 对应于已弃用的(a |@| b).map。
给定类型A、B、C、Error,以下代码sn-ps在cats 1.0.1下编译:
如果您想保留M 的定义(您可能应该),那么您
可以通过将所有内容包装在EitherT 中来避免上述问题,然后
提取value:
import scala.util.Either
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global // required by Monad[Future]
import cats.instances.future._ // Monad for `Future`
import cats.syntax.apply._ // `tupled` and `mapN`
import cats.data.EitherT // EitherT monad transformer
type M[X] = Future[Either[Error, X]]
val ma: M[A] = ???
val mb: M[B] = ???
def doStuff(a: A, b: B): C = ???
val result1: M[C] = (EitherT(ma), EitherT(mb)).mapN(doStuff).value
def doFancyStuff(a: A, b: B): M[C] = ???
val result2: M[C] = (for {
ab <- (EitherT(ma), EitherT(mb)).tupled
c <- EitherT(doFancyStuff(ab._1, ab._2))
} yield c).value
不过,如果这看起来太别扭了,你可以调整M的定义,
以下变体可能会稍微短一些:
import scala.util.Either
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global // required by Monad[Future]
import cats.instances.future._ // Monad for `Future`
import cats.syntax.apply._ // `tupled` and `mapN`
import cats.data.EitherT // EitherT monad transformer
type M[X] = EitherT[Future, Error, X]
val ma: M[A] = ??? // either adjust signatures, or wrap result in EitherT(res)
val mb: M[B] = ???
def doStuff(a: A, b: B): C = ???
val result1: M[C] = (ma, mb).mapN(doStuff)
def doFancyStuff(a: A, b: B): M[C] = ???
val result2: M[C] = (ma, mb).tupled.flatMap{
case (a, b) => doFancyStuff(a, b)
}
这是因为(ma, mb).tupled 构建了一个M[(A, B)],其中M 是前面提到的monad 堆栈,然后可以通过(A, B) => M[C] 函数轻松地将flatMapped 转换为M[C]。
对于带有Cartesian(未经测试)的旧版本
假设(ma, mb).tupled对应deprecated的Cartesian.product,(ma, mb).mapN对应deprecated的(ma |@| mb).map,则上述代码中result1和result2的两个定义为1.0.1 translate到:
val result1: M[C] = (ma |@| mb).map(doStuff)
val result2: M[C] = Cartesian[M].product(ma, mb).flatMap{
case (a, b) => doFancyStuff(a, b)
}
同样,这只是因为Cartesian[M].product(ma, mb) 从M[A] 和M[B] 构建M[(A, B)],其中M[X] 定义为EitherT[Future, Error, X]。如果它被定义为Future[Either[Error, X]],那么flatMap 将在Future 上被调用,而不是doFancyStuff,我们将不得不传递Either[Error, (A, B)] => Future[Either[Error, C]] 之类的东西,这可能不是你想要的。