21

在“Scala 中的函数式编程”一书的“无异常处理错误”一章中,作者给出:

  1. 从函数体抛出异常的问题
  2. 如果Option我们不关心实际的异常,请使用
  3. Either如果我们关心实际的异常,请使用

scala.util.Try并未提及。从我的角度来看,我认为Try在我们关心实际异常时非常合适,为什么不提呢?有什么我错过的原因吗?

4

1 回答 1

49

我不是 Scala 函数式编程的作者,但我可以猜测他们为什么没有提到Try.

有些人不喜欢标准库Try,因为他们声称它违反了函子组合法。我个人认为这个立场有点傻,因为 Josh Suereth 在 SI-6284 的评论中提到的原因,但辩论确实突出了Try设计的一个重要方面。

Try'smap并且flatMap被明确设计为使用可能引发异常的函数。来自 FPiS 学派的人(包括我)倾向于建议将这些函数(如果你绝对必须处理它们)包装在程序中低级别的安全版本中,然后公开一个永远不会抛出的 API (非致命)异常。

在您的 API 中包含Try此模型中的层会混淆——您保证您的 API 方法不会引发异常,但随后您向人们提供了一种旨在与引发异常的函数一起使用的类型。

不过,这只是对标准库的设计和实现的抱怨Try。很容易想象一个Try具有不同语义的版本,其中mapandflatMap方法没有捕获异常,并且仍然有充分的理由尽可能避免这种“改进”版本Try

这些原因之一是使用Either[MyExceptionType, A]而不是Try[A]可以从编译器的穷举检查中获得更多的里程。假设我正在使用以下简单的 ADT 来处理我的应用程序中的错误:

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"
)

现在我正在尝试确定只能以这两种方式之一失败的方法是否应该返回Either[FooAppError, A]Try[A]. 选择Try[A]意味着我们丢弃了对人类用户和编译器都可能有用的信息。假设我写了一个这样的方法:

def doSomething(result: Either[FooAppError, String]) = result match {
  case Right(x) => x
  case Left(MissingField(_)) => "bad"
}

我会收到一个很好的编译时警告,告诉我匹配并不详尽。如果我为丢失的错误添加案例,警告就会消失。

如果我Try[String]改用它,我也会进行详尽检查,但摆脱警告的唯一方法是有一个包罗万象的情况——不可能枚举Throwable模式匹配中的所有 s。

有时我们实际上不能方便地将操作失败的方式限制为我们自己的失败类型(FooAppError如上),在这些情况下我们总是可以使用Either[Throwable, A]. Task例如,Scalaz本质上是Future[Throwable \/ A]. 不同之处在于Either(or \/)支持这种签名,而Try 需要它。由于有用的详尽检查等原因,它并不总是您想要的。

于 2015-07-14T11:25:11.247 回答