我正在关注Coursera的Scala课程。 我也开始阅读Odersky的Scala书。
我经常听到的是,在函数式语言中抛出异常并不是一个好主意,因为它会破坏控制流,我们通常会返回一个带有失败或成功的Either。 似乎Scala 2.10将提供朝着这个方向发展的尝试。
但是在书和课程中,马丁奥德斯基似乎并没有(至少现在)说异常是坏的,他经常使用它们。 我还注意到方法断言/要求......
最后我有点困惑,因为我想遵循最佳做法,但他们不清楚,语言似乎是双向的......
有人能解释一下我应该在哪种情况下使用什么?
答案 0 :(得分:47)
基本准则是对非常特殊的事物使用例外**。对于“普通”失败,使用Option
或Either
要好得多。如果您正在与Java接口,当有人以错误的方式打喷嚏时会抛出异常,您可以使用Try
来保证自己的安全。
我们来举一些例子。
假设您有一个从地图中获取内容的方法。怎么可能出错?好吧,像 segfault *堆栈溢出一样戏剧性和危险的东西,或者找不到像元素一样的东西。你让 segfault 堆栈溢出抛出一个异常,但如果你只是找不到一个元素,为什么不返回Option[V]
而不是值或异常(或{{ 1}})?
现在假设您正在编写一个用户应该输入文件名的程序。现在,如果您不是在出现问题时立即对该计划保释,那么null
就可以了:
Either
现在假设您要解析带有空格分隔数字的字符串。
def main(args: Array[String]) {
val f = {
if (args.length < 1) Left("No filename given")
else {
val file = new File(args(0))
if (!file.exists) Left("File does not exist: "+args(0))
else Right(file)
}
}
// ...
}
所以你有三种方法(至少)可以处理不同类型的失败:val numbers = "1 2 3 fish 5 6" // Uh-oh
// numbers.split(" ").map(_.toInt) <- will throw exception!
val tried = numbers.split(" ").map(s => Try(s.toInt)) // Caught it!
val good = tried.collect{ case Success(n) => n }
因为它没有工作/没有,如果不工作是预期的行为,不是令人震惊和惊人的失败; Option
当事情可以起作用或不起作用(或者,实际上,任何情况下你有两个相互排斥的选项)并且你想保存一些关于出错的信息;和Either
当你不想自己处理异常处理时,但仍然需要与异常快乐的代码接口。
顺便提一下,例外情况就是一个很好的例子 - 所以你会发现它们在教科书或学习材料中比其他地方更常见,我认为:教科书的例子往往是不完整的,这意味着通常可以通过谨慎的设计应该通过抛出异常来标记。
* 编辑:Segfaults崩溃了JVM,不管字节码如何都不应该发生;即使例外也无济于事。我的意思是堆栈溢出。
** 编辑:异常(没有堆栈跟踪)也用于Scala中的控制流 - 它们实际上是一种非常有效的机制,它们支持库定义的Try
语句之类的东西以及从您的方法返回的break
,即使控件实际上已经传递到一个或多个闭包中。大多数情况下,您不应该自己担心,除非意识到捕获所有 return
并不是一个超级想法,因为您可能会错误地捕获其中一个控制流异常。 / SUP>
答案 1 :(得分:11)
因此,这是Scala专门为传统语言和环境(特别是Java)转换功能纯度以实现易于转换/互操作性的地方之一。功能纯度被例外打破,因为它们破坏了参照完整性并使得不可能在等同上进行推理。 (当然,非终止递归也是如此,但是很少有语言愿意强制执行那些使这些不可能的限制。)为了保持功能纯度,你使用Option / Maybe / Either / Try / Validation,所有这些都编码成功或者作为引用透明类型的失败,并使用它们提供的各种高阶函数或基础语言特殊的monad语法来使事情更清楚。或者,在Scala中,你可以简单地决定放弃功能纯度,知道它可能会在短期内使事情变得更容易,但在长期内会更加困难。这类似于使用&#34; null&#34;在Scala,或可变集合,或本地&#34; var&#34; s。轻微的可耻,并不是很多,但每个人都在截止日期之前。