This is what Scalaz's EitherT
monad transformer is for. A stack of IO[Either[E, A]]
is equivalent to EitherT[IO, E, A]
, except that the former must be handled as multiple monads in sequence, whereas the latter is automatically a single monad that adds Either
capabilities to the base monad IO
. You can likewise use EitherT[Future, E, A]
to add non-exceptional error handling to asynchronous operations.
Monad transformers in general are the answer to a need to mix multiple monads in a single for
-comprehension and/or monadic operation.
EDIT:
I will assume you are using Scalaz version 7.0.0.
In order to use the EitherT
monad transformer on top of the IO
monad, you first need to import the relevant parts of Scalaz:
import scalaz._, scalaz.effect._
You also need to define your error types: RepositoryError
, BusinessError
, etc. This works as usual. You just need to make sure that you can, e.g., convert any RepositoryError
into a BusinessError
and then pattern match to recover the exact type of error.
Then the signatures of your methods become:
def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]
Within each of your methods, you can use the EitherT
-and-IO
-based monad stack as a single, unified monad, available in for
-comprehensions as usual. EitherT
will take care of threading the base monad (in this case IO
) through the whole computation, while also handling errors the way Either
usually does (except already right-biased by default, so you don't have to constantly deal with all the usual .right
junk). When you want to do an IO
operation, all you have to do is raise it into the combined monad stack by using the liftIO
instance method on IO
.
As a side note, when working this way, the functions in the EitherT
companion object can be very useful.