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]]
上 flatMap
在 EitherT[Future, Error, X]
-monad 堆栈的意义上是尴尬的,因为原始的 flatMap
Future
妨碍了,编译器不会寻找可以同时处理 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 堆栈,然后可以很容易地 flatMap
与 (A, B) => M[C]
函数到 M[C]
.
对于具有 Cartesian
的旧版本(未测试)
假设(ma, mb).tupled
对应deprecated的Cartesian.product
,(ma, mb).mapN
对应deprecated的(ma |@| mb).map
,result1
和[=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]]
的东西,这可能不是你想要的。
鉴于:
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]]
上 flatMap
在 EitherT[Future, Error, X]
-monad 堆栈的意义上是尴尬的,因为原始的 flatMap
Future
妨碍了,编译器不会寻找可以同时处理 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 堆栈,然后可以很容易地 flatMap
与 (A, B) => M[C]
函数到 M[C]
.
对于具有 Cartesian
的旧版本(未测试)
假设(ma, mb).tupled
对应deprecated的Cartesian.product
,(ma, mb).mapN
对应deprecated的(ma |@| mb).map
,result1
和[=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]]
的东西,这可能不是你想要的。