Given:
import cats.syntax.cartesian._
type M[X] = Future[Either[Error, X]]
val ma: M[A] = ???
val mb: M[B] = ???
I know I can do that:
def doStuff(a: A, b: B): C = ???
val result: M[C] = (ma |@| mb).map(doStuff)
But how do I flatMap? There's no flatMap in the CartesianBuilders.
def doFancyStuff(a: A, b: B): M[C] = ???
val result: M[C] = (ma |@| mb).flatMap(doFancyStuff)
I think the main problem is that it's awkward to flatMap on a Future[Either[Error, X]] in the sense of EitherT[Future, Error, X]-monad stack, because the original flatMap of the Future gets in the way, and the compiler isn't looking for a monad instance that could handle the combination of Future and Either simultaneously. However, if you wrap the futures in EitherT, everything works smoothly.
For cats 1.0.1
In the following, (a,b).tupled corresponds to Cartesian.product(a, b), and (a, b).mapN corresponds to the deprecated (a |@| b).map.
Given types A, B, C, Error, the following code snippets compile under cats 1.0.1:
If you want to preserve your definition of M (you probably should), then you
can avoid the above mentioned problems by wrapping everything in EitherT and then
extracting the 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
However, if this seems too awkward, and you can adjust the definition of M,
the following variant could be slightly shorter:
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)
}
This is because (ma, mb).tupled builds a M[(A, B)], where M is the previously mentioned monad stack, which then can be easily flatMapped with a (A, B) => M[C] function to M[C].
For older versions with Cartesian (untested)
Assuming that (ma, mb).tupled corresponds to the deprecated Cartesian.product and (ma, mb).mapN corresponds to the deprecated (ma |@| mb).map, the two definitions of result1 and result2 in the above code snippet for 1.0.1 translate to:
val result1: M[C] = (ma |@| mb).map(doStuff)
val result2: M[C] = Cartesian[M].product(ma, mb).flatMap{
case (a, b) => doFancyStuff(a, b)
}
Again, this works only because Cartesian[M].product(ma, mb) builds an M[(A, B)] from M[A] and M[B], where M[X] is defined as EitherT[Future, Error, X]. If it were defined as Future[Either[Error, X]], then the flatMap would be invoked on the Future, and instead of doFancyStuff we would have to pass something like Either[Error, (A, B)] => Future[Either[Error, C]], which is probably not what you want.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With