Scala 猫,笛卡尔平面地图?

Scala cats, flatMap on cartesian?

鉴于:

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)

但是我怎么flatMap呢? CartesianBuilder 中没有 flatMap

def doFancyStuff(a: A, b: B): M[C] = ???
val result: M[C] = (ma |@| mb).flatMap(doFancyStuff)

我认为主要问题是在 Future[Either[Error, X]]flatMapEitherT[Future, Error, X]-monad 堆栈的意义上是尴尬的,因为原始的 flatMap Future 妨碍了,编译器不会寻找可以同时处理 FutureEither 组合的 monad 实例。但是,如果将期货包装在 EitherT 中,一切都会顺利进行。

对于猫 1.0.1

下面,(a,b).tupled对应Cartesian.product(a, b)(a, b).mapN对应废弃的(a |@| b).map

给定类型 ABCError,以下代码片段在 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 堆栈,然后可以很容易地 flatMap(A, B) => M[C] 函数到 M[C].

对于具有 Cartesian 的旧版本(未测试)

假设(ma, mb).tupled对应deprecated的Cartesian.product(ma, mb).mapN对应deprecated的(ma |@| mb).mapresult1和[=45的两个定义=] 在上面的 1.0.1 代码片段中转换为:

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]] 的东西,这可能不是你想要的。