In the chapter \"Handling errors without exceptions\" of book \"functional programming in Scala\", the author gives:
I'm neither of the authors of Functional Programming in Scala, but I can make a few guesses about why they don't mention Try
.
Some people don't like the standard library's Try
because they claim it violates the functor composition law. I personally think that this position is kind of silly, for the reasons Josh Suereth mentions in the comments of SI-6284, but the debate does highlight an important aspect of Try
's design.
Try
's map
and flatMap
are explicitly designed to work with functions that may throw exceptions. People from the FPiS school of thought (including me) would tend to suggest wrapping such functions (if you absolutely have to deal with them at all) in safe versions at a low level in your program, and then exposing an API that will never throw (non-fatal) exceptions.
Including Try
in your API muddles up the layers in this model—you're guaranteeing that your API methods won't throw exceptions, but then you're handing people a type that's designed to be used with functions that throw exceptions.
That's only a complaint about the standard library's design and implementation of Try
, though. It's easy enough to imagine a version of Try
with different semantics, where the map
and flatMap
methods didn't catch exceptions, and there would still be good reasons to avoid this "improved" version of Try
whenever possible.
One of these reasons is that using Either[MyExceptionType, A]
instead of Try[A]
makes it possible to get more mileage out of the compiler's exhaustivity checking. Suppose I'm using the following simple ADT for errors in my application:
sealed class FooAppError(message: String) extends Exception(message)
case class InvalidInput(message: String) extends FooAppError(message)
case class MissingField(fieldName: String) extends FooAppError(
s"$fieldName field is missing"
)
Now I'm trying to decide whether a method that can only fail in one of these two ways should return Either[FooAppError, A]
or Try[A]
. Choosing Try[A]
means we're throwing away information that's potentially useful both to human users and to the compiler. Suppose I write a method like this:
def doSomething(result: Either[FooAppError, String]) = result match {
case Right(x) => x
case Left(MissingField(_)) => "bad"
}
I'll get a nice compile-time warning telling me that the match is not exhaustive. If I add a case for the missing error, the warning goes away.
If I had used Try[String]
instead, I'd also get exhaustivity checking, but the only way to get rid of the warning would be to have a catch-all case—it's just not possible to enumerate all Throwable
s in the pattern match.
Sometimes we actually can't conveniently limit the kinds of ways an operation can fail to our own failure type (like FooAppError
above), and in these cases we can always use Either[Throwable, A]
. Scalaz's Task
, for example, is essentially a wrapper for Future[Throwable \/ A]
. The difference is that Either
(or \/
) supports this kind of signature, while Try
requires it. And it's not always what you want, for reasons like useful exhaustivity checking.