Search code examples
scalamonadsscalazmonad-transformers

Monad transformer for Future[Either[Error, Option[User]]]


Consider the signature of retrieveUser where retrieving a non-existent user is not modelled as an error, that is, it is modelled as Future[Right[None]]:

def retrieveUser(email: String): Future[Either[Error, Option[User]]]

Does there exist a monad transformer MT such that we can write

(for {
  user <- MT(retrieveUser(oldEmail))
  _    <- MT(updateUser(user.setEmail(newEmail)))
} {}).run

Using EitherT the best I can do is the following:

EitherT(retrieveUser(oldEmail)).flatMap {
  case Some(user) =>
    EitherT(updateUser(user.setEmail(newEmail)))

  case None => 
    EitherT.right(Future.successful({}))
}.run

The problem is that mapping over EitherT(retrieveUser(email)) results in Option[User], instead of unboxed User, which breaks the for-comprehension.


Solution

  • I assume that the order of parameters is the same as in EitherT form Scala Cats: an EitherT[F[_], A, B] is essentially just a wrapper around F[Either[A, B]].

    Likewise, OptionT[F, A] is wrapper around F[Option[A]].

    Thus,

    OptionT[EitherT[Future, Error, ?], A]
    

    is a wrapper around

    EitherT[Future, Error, Option[A]]
    

    which is in turn a wrapper around

    Future[Either[Error, Option[A]]]
    

    Therefore,

    OptionT[EitherT[Future, Error, ?], User](
      EitherT[Future, Error, Option[User]](retrieveUser(oldEmail))
    )
    

    should typecheck (with the non/kind-projector), and with -Ypartial-unification the types should also be inferred automatically, so you could try to use

    OptionT(EitherT(retrieveUser(oldEmail))
    

    inside the for-comprehensions.