I have a monad stack for responses from my components implemented with the cats monads transformers:
type FutureEither[A] = EitherT[Future, Error, A]
type FutureEitherOption[A] = OptionT[FutureEither, A]
the result is effectively:
Future[Either[Error, Option[A]]]
How can I get a value or error from this stack in a proper way? How can I combine the results from several calls execute in parallel in a proper way? For example in such scenario:
def fooServiceCall: FutureEitherOption[Foo]
def barServiceCall(f: Option[Foo]): FutureEitherOption[Bar]
for {
r1 <- fooServiceCall
r2 <- barServiceCall(r1)
} yield r2
Your second method barServiceCall
tells in its signature that it can deal with Option[Foo]
directly, instead of relying on the monad transformer stack to fail with None
at some point. Therefore, you have to unpack one layer of OptionT[EitherT[Future, Error, ?], A]
, and instead deal with EitherT[Future, Error, Option[A]]
directly: even though your methods seem to return results in the former monad stack, the right tool in this for-comprehension is the latter one.
Recall that if o: OptionT[F, A]
, then the wrapped o.value
is of type F[Option[A]]
.
Therefore, if you simply call .value
on a OptionT[EitherT[Future, Error, ?], A]
, you get the required EitherT[Future, Error, Option[A]]
. Here is how it works out in code:
import scala.concurrent.Future
import scala.util.Either
import cats.instances.future._
import cats.instances.either._
import cats.instances.option._
import cats.data.EitherT
import cats.data.OptionT
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
type Error = String // or whatever...
type Foo = (Int, Int) // whatever...
type Bar = (String, Float) // whatever...
type FutureEither[A] = EitherT[Future, Error, A]
type FutureEitherOption[A] = OptionT[FutureEither, A]
def fooServiceCall: FutureEitherOption[Foo] = ???
def barServiceCall(f: Option[Foo]): FutureEitherOption[Bar] = ???
val resFut: Future[Either[Error, Option[Bar]]] = (for {
r1 <- fooServiceCall.value // : EitherT[Future, Error, Option[Foo]]
r2 <- barServiceCall(r1).value // : EitherT[Future, Error, Option[Bar]]
} yield r2).value
val res: Either[Error, Option[Bar]] = Await.result(resFut, 10.seconds)
Now the result is simple enough that you can deal with it direcly.
Alternatively, if you don't want to unpack the result here and now, you can wrap it again into an OptionT
and keep working with a FutureEitherOption[Bar]
:
val res2: FutureEitherOption[Bar] =
OptionT(for {
r1 <- fooServiceCall.value
r2 <- barServiceCall(r1).value
} yield r2)