给定:
import cats.syntax.cartesian._
type M[X] = Future[Either[Error, X]]
val ma: M[A] = ???
val mb: M[B] = ???
我知道我可以做到:
def doStuff(a: A, b: B): C = ???
val result: M[C] = (ma |@| mb).map(doStuff)
但是我如何平面地图?CartesianBuilder
没有flatMap
。
def doFancyStuff(a: A, b: B): M[C] = ???
val result: M[C] = (ma |@| mb).flatMap(doFancyStuff)
我认为主要问题是在EitherT[Future, Error, X]
-monad 堆栈的意义上flatMap
Future[Either[Error, X]]
很尴尬,因为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
,以下代码片段在 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]
函数flatMap
M[C]
。
对于具有Cartesian
的旧版本(未经测试)
假设(ma, mb).tupled
对应于已弃用的Cartesian.product
,(ma, mb).mapN
对应于已弃用的(ma |@| mb).map
,则上述 1.0.1 代码片段中result1
和result2
的两个定义转换为:
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]]
的东西,这可能不是你想要的。