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.
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.