Try[Result], IO[Result], Either[Error,Result], which should I use in the end
either, scala, scalaz
Solution
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.
Problem
I'd like to know what should be the signature of my methods so that I handle different kind of failures elegantly. This question is somehow the summary of many questions I already had about error handling in Scala. You can find some questions here: - Throwing exceptions in Scala, what is the "official rule" - Either, Options and for comprehensions - Either monadic operations For now, I understand the following: - Either can be used as a result wrapper for a method call that may fail - Try is a Right biaised Either on which the failure is a non-fatal exception - IO (scalaz) helps to build pure methods that handle IO operations - All 3 are easily usable in a for comprehension - All 3 are not easily mixable in a for comprehension because of incompatible flatMap methods - In functional langages we usually don't throw exceptions unless they are fatal - We should throw exceptions for really exceptional conditions. I guess this is the approach of Try - Creating Throwables has a performance cost for the JVM and it is not intended to be used for business flow controle Repository layer Now please consider that I have a `UserRepository`. The `UserRepository` stores the users and defines a `findById` method. The following failures could happen: - A fatal failure (`OutOfMemoryError`) - An IO failure because the database is not accessible/readable Additionally, the user could be missing, leading to an `Option[User]` result Using a JDBC implementation of the repository, SQL, non-fatal exceptions (constraint violation or others) can be thrown so it can make sense to use Try. As we are dealing with IO operations, then the IO monad also makes sense if we want pure functions. So the result type could be: - `Try[Option[User]]` - `IO[Option[User]]` - something else? Service layer Now let's introduce a business layer, `UserService`, which provides some method `updateUserName(id,newUserName)` that uses the previously defined `findById` of the repository. The following failures could happen: - All repository failures propagated to the Service layer - Business error: can't update the username of an user that doesn't exist - Business error: the new username is too short Then the result type could be: - `Try[Either[BusinessError,User]]` - `IO[Either[BusinessError,User]]` - something else? BusinessError here is not a Throwable because it is not an exceptional failure. Using for-comprehensions I would like to keep using for-comprehensions to combine method calls. We can't easily mix different monads on a for-comprehension, so I guess I should have some kind of uniform return type for all my operations right? I just wonder how do you succeed, in your real world Scala applications, to keep using for-comprehensions when different kind of failures can happen. For now, for-comprehension works fine for me, using services and repositories which all return `Either[Error,Result]` but all different kind of failures are melted together and it becomes kind of hacky to handle these failures. Do you define implicit conversions between different kind of monads to be able to use for-comprehensions? Do you define your own monads to handle failures? By the way perhaps I'll be using an asynchronous IO driver soon. So I guess my return type could be even more complicated: `IO[Future[Either[BusinessError,User]]]` Any advice would be welcome because I don't really know what to use, while my application is not fancy: it is just an API where I should be able to make a distinction between business errors that can be shown to the client side, and technical errors. I try to find an elegant and pure solution.