Search code examples
scalascala-catsmonad-transformersmonix

Different monads in for comprehension


I have below code


def processInfoAndReturnResponse(input: Input]): EitherT[Future, CustomException, A] = ???

def sendMessage(message: A): monix.eval.Task[Boolean] = ???

def anotherMethod(message: Input): Future[Either[CustomException, Unit]]= ???
def integrate(): Future[Either[GoogleException, A]] = {
(for {
  response <- processInfoAndModelResponse(input)
  _ <- EitherT.liftF[Future, CustomException, A](sendMessage(response).map(_ => response).runToFuture
}yield response).value

so far all this is good. But now, I want to get boolean from sendMessage and then only if sendMessage returns true, I want to call anotherMethod.

I understand they are different monads. Please let me know a cleaner way how I can add all three calls in for comprehension. Appreciate help


Solution

  • Unfortunately, EitherT and Task are different monads and monads doesn't compose so you can't use them directly in the same for comprehension.

    What you could do is lifting Task into EitherT but in this case type parameter F of EitherT would have to be Task and in your case it's Future.

    So you have to do 2 things:

    1. Transform Task into Future
    2. Lift Future to EitherT

    Let's say your another method looks like this:

    def anotherMethod(input: Integer): EitherT[Future, Exception, Unit] = EitherT.rightT[Future, Exception](())
    

    So your for-comprehension could look like this:

    import cats.implicits._
    import scala.concurrent.ExecutionContext.Implicits._
    
    val prog = for {
        //you need to use leftWiden adjust left of either to common type
        response <- processInfoAndReturnResponse(inp).leftWiden[Exception]
        //running task to Future and then lifting to EitherT
        i <- EitherT.liftF[Future, Exception, Integer](sendMessage(response).runToFuture)
        _ <- anotherMethod(i)
    } yield ()
    
    //prog is of type EitherT so we have to unwrap it to regular Future with rethrowT
    val future: Future[Unit] = prog.rethrowT
    

    To answer your question after edit, you can use whenA to conditionaly use effect in for-comprehension:

    def integrate(): Future[Either[GoogleException, A]] ={
      (for {
        response <- processInfoAndModelResponse(input)
        sendStatus <- EitherT.liftF[Future, CustomException, Boolean](sendMessage(response).runToFuture)
        finalresult <- anotherMethod(input).whenA(sendStatus)
      } yield finalresult).value
    }