5
votes

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.

1
Is it supposed to be Scalaz or Scala-Cats?Andrey Tyukin
OptionT(EitherT(retrieveUser(oldEmail)) with -Ypartial-unification worked fine in Scalaz.Mario Galic

1 Answers

3
votes

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.