为什么 "functional programming in Scala" 书的 "Handling errors without exceptions" 章中没有提到 `scala.util.Try`?
Why `scala.util.Try` is not mentioned in chapter "Handling errors without exceptions" of book "functional programming in Scala"?
在书 "functional programming in Scala" 的第 "Handling errors without exceptions" 章中,作者给出:
- 函数体抛出异常的问题
- 如果我们不关心实际异常就使用
Option
- 如果我们关心实际异常,请使用
Either
但是没有提到scala.util.Try
。从我的角度来看,我认为 Try
当我们关心实际异常时非常适合,为什么没有提到它?有什么我错过的原因吗?
我不是《Scala 函数式编程》的作者,但我可以猜测他们为什么没有提到 Try
。
有些人不喜欢标准库的 Try
,因为他们声称 it violates the functor composition law。我个人认为这个立场有点愚蠢,因为 Josh Suereth 在 SI-6284 的评论中提到的原因,但辩论确实突出了 Try
设计的一个重要方面。
Try
的 map
和 flatMap
明确设计用于可能引发异常的函数。 FPiS 思想流派的人(包括我)倾向于建议将这些函数(如果你绝对必须处理它们)包装在程序低级别的安全版本中,然后公开一个 API永远不会抛出(非致命)异常。
在你的 API 中包含 Try
会混淆这个模型中的层——你保证你的 API 方法不会抛出异常,但是你正在处理people 一种设计用于引发异常的函数的类型。
不过,这只是对标准库设计和实现 Try
的抱怨。很容易想象 Try
具有不同语义的版本,其中 map
和 flatMap
方法没有捕获异常,并且仍然有充分的理由避免这种 "improved" 版本 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"
)
现在我正在尝试确定一种方法 只能 以这两种方式之一失败应该 return 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
。
有时我们实际上不能方便地将操作失败的方式限制为我们自己的失败类型(如上面的FooAppError
),在这些情况下我们总是可以使用Either[Throwable, A]
。例如,Scalaz 的 Task
本质上是 Future[Throwable \/ A]
的包装器。区别在于Either
(或\/
)支持这种签名,而Try
需要它。由于有用的详尽检查等原因,它并不总是您想要的。
在书 "functional programming in Scala" 的第 "Handling errors without exceptions" 章中,作者给出:
- 函数体抛出异常的问题
- 如果我们不关心实际异常就使用
Option
- 如果我们关心实际异常,请使用
Either
但是没有提到scala.util.Try
。从我的角度来看,我认为 Try
当我们关心实际异常时非常适合,为什么没有提到它?有什么我错过的原因吗?
我不是《Scala 函数式编程》的作者,但我可以猜测他们为什么没有提到 Try
。
有些人不喜欢标准库的 Try
,因为他们声称 it violates the functor composition law。我个人认为这个立场有点愚蠢,因为 Josh Suereth 在 SI-6284 的评论中提到的原因,但辩论确实突出了 Try
设计的一个重要方面。
Try
的 map
和 flatMap
明确设计用于可能引发异常的函数。 FPiS 思想流派的人(包括我)倾向于建议将这些函数(如果你绝对必须处理它们)包装在程序低级别的安全版本中,然后公开一个 API永远不会抛出(非致命)异常。
在你的 API 中包含 Try
会混淆这个模型中的层——你保证你的 API 方法不会抛出异常,但是你正在处理people 一种设计用于引发异常的函数的类型。
不过,这只是对标准库设计和实现 Try
的抱怨。很容易想象 Try
具有不同语义的版本,其中 map
和 flatMap
方法没有捕获异常,并且仍然有充分的理由避免这种 "improved" 版本 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"
)
现在我正在尝试确定一种方法 只能 以这两种方式之一失败应该 return 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
。
有时我们实际上不能方便地将操作失败的方式限制为我们自己的失败类型(如上面的FooAppError
),在这些情况下我们总是可以使用Either[Throwable, A]
。例如,Scalaz 的 Task
本质上是 Future[Throwable \/ A]
的包装器。区别在于Either
(或\/
)支持这种签名,而Try
需要它。由于有用的详尽检查等原因,它并不总是您想要的。