Why `scala.util.Try` is not mentioned in chapter “Handling errors without exceptions” of book “functional programming in Scala”?

后端 未结 1 1956
广开言路
广开言路 2021-01-30 15:00

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

  1. The problem of throwing exceptions from the bo
相关标签:
1条回答
  • 2021-01-30 15:26

    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.

    0 讨论(0)
提交回复
热议问题