21

In the chapter "Handling errors without exceptions" of book "functional programming in Scala", the author gives:

  1. The problem of throwing exceptions from the body of a function
  2. Use Option if we don't care about the actual exception
  3. Use Either if we care about the actual exception

But scala.util.Try is not mentioned. From my point of view, I think Try is very suitable when we care about the actual exception, why it's not mentioned? Is there any reason I have missed?

pnuts
  • 54,806
  • 9
  • 74
  • 122
Freewind
  • 177,284
  • 143
  • 381
  • 649
  • 1
    My guess is that the book was written before Try was added to the Scala standard library. Try was added in version 2.10. – marstran Jul 14 '15 at 10:54
  • 2
    On page 58 and 61 they implement their own `Try` function using their definitions of resp `Option` and `Either`. _FP in Scala_ is not a book about Scala but about Functional Programming (in Scala), that's why the exercises reimplement parts of the existing Scala API to learn the concept of FP. – Peter Neyens Jul 14 '15 at 11:15
  • 1
    @marstran `Try` has been around since 2012 and the book was published this year. If `Try` was relevant and the authors had wanted to discuss it, they would have. – Travis Brown Jul 14 '15 at 11:32
  • @PeterNeyens That's a good thought, but in the book, it mentions that there are built-in `Option` and `Either` in Scala sdk – Freewind Jul 14 '15 at 14:08
  • When I used Try. I found it tended to become invasive across function/method return types, a bit like checked exceptions in Java, i.e. Try[] just started to appear everywhere, cluttering up the source code. Of course, YMMV. – satyagraha Jul 16 '15 at 23:15

1 Answers1

49

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

Travis Brown
  • 135,682
  • 12
  • 352
  • 654
  • As a footnote, see my answer [here](http://stackoverflow.com/a/21665747/334519) for some additional discussion. – Travis Brown Jul 14 '15 at 11:27
  • 3
    Good answer, though I think you seriously undersell the value of `Try` in taming unruly external code. Wrapping things takes time. `Try` is awesome when things can break in all sorts of ways but it's still valuable to get an unbroken answer if you can have one. – Rex Kerr Jul 15 '15 at 08:33
  • 1
    @RexKerr Agreed! At my day job we rely heavily on `Try` (our own, but it's essentially the same as the standard library's), and if the alternative is exceptions everywhere `Try` is fantastic. I guess I could make it a little clearer that I'm answering the question "why might some people not like `Try`?", not "should I avoid `Try`?". – Travis Brown Jul 15 '15 at 13:31
  • 1
    I think catching exceptions in map/flatMap was the right call. Think what would happen if Try would be async - without explicitly catching exceptions, they'd leave no trace, silently killing the thread where they are executed. As it happens Future is async and the equivalent of Try. Without catching the exception in a flatMap, you wouldn't be able to recover from it. And I agree with you that an Either better signals possible errors, but Try is still useful for situations in which you can have unexpected exceptions. And for example I/O operations can lead to unplanned exceptions. – Alexandru Nedelcu Apr 18 '16 at 09:05
  • 1
    I also think that catching exceptions in map/flatMap is the right call because people expect "safety" while in Try's context. People expect that because that's why they are using Try in the first place. This is the principle of least astonishment. – Alexandru Nedelcu Apr 18 '16 at 09:06
  • What's the benefit of `extend`-ing `Exception` in the ADT of `FooAppError`? – Kevin Meredith Sep 12 '16 at 14:47