scalamonad-transformersscala-catswriter-monad

Stacking monads Writer and OptionT


I have the following code:

override def getStandsByUser(email: String): Try[Seq[Stand]] =
  (for {
    user <- OptionT(userService.findOneByEmail(email)): Try[Option[User]]
    stands <- OptionT.liftF(standService.list()):[Try[List[Stand]]]
    filtered = stands.filter(stand => user.stands.contains(stand.id))
  } yield filtered).getOrElse(Seq())
}

I want to add logging on each stage of the processing - so I need to introduce writer monad and stack it with monad transformer OptionT. Could you please suggest how to do that?


Solution

  • The best way to do this is to convert your service calls into using cats-mtl.

    For representing Try or Option you can use MonadError and for logging you can use FunctorTell. Now I don't know what exactly you're doing inside your userService or standService, but I wrote some code to demonstrate what the result might look like:

    type Log = List[String]
    
    //inside UserService
    def findOneByEmail[F[_]](email: String)
      (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[User] = ???
    
    //inside StandService
    def list[F[_]]()
      (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[List[Stand]] = ???
    
    def getStandsByUser[F[_]](email: String)
    (implicit F: MonadError[F, Error], W: FunctorTell[F, Log]): F[List[Stand]] =
      for {
        user <- userService.findOneByEmail(email)
        stands <- standService.list()
      } yield stands.filter(stand => user.stands.contains(stand.id))
    
    
    //here we actually run the function
    val result =
      getStandsByUser[WriterT[OptionT[Try, ?], Log, ?] // yields WriterT[OptionT[Try, ?], Log, List[Stand]]
        .run  // yields OptionT[Try, (Log, List[Stand])]
        .value // yields Try[Option[(Log, List[Stand])]]
    

    This way we can avoid all of the calls to liftF and easily compose our different services even if they will use different monad transformers at runtime.