Search code examples
scalamonadsmonad-transformersscala-cats

Cats get value from the monad stack


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

Solution

  • 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)