scala 2.8控件异常 - 有什么意义?

时间:2009-10-29 16:07:53

标签: scala exception-handling

在即将推出的scala 2.8中,添加了一个util.control包,其中包含一个中断库和一个用于处理异常的构造,以便代码看起来像:

type NFE = NumberFormatException
val arg = "1"
val maybeInt = try { Some(arg.toInt) } catch { case e: NFE => None }

可以用以下代码替换:

import util.control.Exception._
val maybeInt = catching(classOf[NFE]) opt arg.toInt

我的问题是为什么?除了提供另一种(完全不同的)表达同一事物的方式之外,这又是什么呢?是否有任何可以使用新控件但不能通过try-catch表达的内容?这是一个DSL应该在Scala中使异常处理看起来像其他语言(如果是这样,哪一个)?

3 个答案:

答案 0 :(得分:28)

有两种方法可以考虑异常。一种方法是将它们视为流控制:异常会改变程序的执行流程,使执行从一个地方跳到另一个地方。第二种方法是将它们视为数据:例外是关于程序执行的信息,然后可以将其用作程序其他部分的输入。

C ++和Java中使用的try / catch范例是第一种(*)。

但是,如果您希望将异常作为数据处理,那么您将不得不求助于显示的代码。对于简单的情况,这很容易。然而,当涉及到构图为王的功能风格时,事情开始变得复杂。你要么必须在周围重复代码,要么你自己的库来处理它。

因此,在一种声称同时支持功能和OO风格的语言中,看到图书馆支持将异常视为数据时,不应感到惊讶。

请注意Exception处理事物提供了许多其他可能性。例如,您可以链式捕获处理程序,就像Lift链的部分功能一样,可以轻松地将责任委托给处理网页请求。

这是可以做什么的一个例子,因为如今自动资源管理很流行:

def arm[T <: java.io.Closeable,R](resource: T)(body: T => R)(handlers: Catch[R]):R = (
  handlers 
  andFinally (ignoring(classOf[Any]) { resource.close() }) 
  apply body(resource)
)

这使您可以安全地关闭资源(注意使用忽略),并且仍然应用您可能想要使用的任何捕获逻辑。

(*)奇怪的是,Forth的异常控制catch&amp; throw是它们的混合体。流程从throw跳转到catch,但之后该信息被视为数据。

修改

好的,好的,我屈服了。我举个例子。一个例子,就是这样!我希望这不是太做作,但没有办法绕过它。这种事情在大型框架中最有用,而不是小样本。

无论如何,我们首先要定义与资源有关的事情。我决定打印行并返回打印的行数,这里是代码:

def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
  var lineNumber = 0
  var lineText = lnr.readLine()
  while (null != lineText) {
    lineNumber += 1
    println("%4d: %s" format (lineNumber, lineText))
    lineText = lnr.readLine()
  }
  lineNumber
} _

以下是此功能的类型:

linePrinter: (lnr: java.io.LineNumberReader)(util.control.Exception.Catch[Int]) => Int

所以,arm收到了一个通用的Closeable,但我需要一个LineNumberReader,所以当我调用这个函数时,我需要传递它。但是,我返回的是一个函数Catch[Int] => Int,这意味着我需要将两个参数传递给linePrinter才能使其工作。我们想出一个Reader,现在:

val functionText = """def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
  var lineNumber = 1
  var lineText = lnr.readLine()
  while (null != lineText) {
    println("%4d: %s" format (lineNumber, lineText))
    lineNumber += 1
    lineText = lnr.readLine()
  }
  lineNumber
} _"""

val reader = new java.io.LineNumberReader(new java.io.StringReader(functionText))

所以,现在,让我们使用它。首先,一个简单的例子:

scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
   1: def linePrinter(lnr: java.io.LineNumberReader) = arm(lnr) { lnr =>
   2:          var lineNumber = 1
   3:          var lineText = lnr.readLine()
   4:          while (null != lineText) {
   5:            println("%4d: %s" format (lineNumber, lineText))
   6:            lineNumber += 1
   7:            lineText = lnr.readLine()
   8:          }
   9:          lineNumber
  10:        } _
res6: Int = 10

如果我再试一次,我就明白了:

scala> linePrinter(new java.io.LineNumberReader(reader))(noCatch)
java.io.IOException: Stream closed

现在假设我想在发生任何异常时返回0。我可以这样做:

linePrinter(new java.io.LineNumberReader(reader))(allCatch withApply (_ => 0))

这里有趣的是我完全解除了异常处理catch / trycatch部分资源,通过finally完成。此外,错误处理是我可以传递给函数的值。至少,它可以更轻松地模拟try / catch / finally语句。 : - )

此外,我可以使用Catch方法组合多个or,以便我的代码的不同层可能会选择为不同的异常添加不同的处理程序。这真的是我的主要观点,但我找不到一个异常丰富的界面(在我看的简短时间内:)。

我最后会谈到我给出的arm的定义。这不是一个好的。特别是,我无法使用CatchtoEithertoOption方法将结果从R更改为其他内容,从而严重降低使用{{1}的值}} 在里面。不过,我不知道如何改变它。

答案 1 :(得分:9)

作为撰写它的人,原因是构图和封装。 scala编译器(我打赌最合适的源代码库)充满了吞噬所有异常的地方 - 你可以通过-Ywarn-catches看到这些 - 因为程序员懒得枚举相关的,这是可以理解的因为它很烦人通过使得可以独立于try逻辑来定义,重用和组合catch以及最终阻塞,我希望降低编写敏感块的障碍。

并且,它还没有完成,而且我还在其他一百万个领域工作。如果你查看actor代码,你可以看到巨大的剪切和粘贴的多重嵌套try / catch / finally块的例子。我/我不愿意接受try { catch { try { catch { try { catch ...

我最终的计划是让捕捉得到一个实际的PartialFunction,而不是需要一个案例陈述的文字列表,这提供了一个全新的机会,即try foo() catch getPF()

答案 2 :(得分:3)

我说这首先是表达方式的问题。

除了比它的等价物更短之外,新的capture()方法提供了一种表达相同行为的更多功能方法。尝试...捕获语句通常被认为是命令式样式,异常被认为是副作用。 Catching()为这个命令性代码添加了一条毯子,以便将其隐藏起来。

更重要的是,现在我们有了一个功能,那么它可以更容易地与其他东西组合;它可以传递给其他更高阶的函数来创建更复杂的行为。 (您不能直接传递带参数化异常类型的try .. catch语句。)

另一种看待这种情况的方法是Scala不提供这个capture()函数。然后,很可能人们会独立“重新发明”它,这将导致代码重复,并导致更多的非标准代码。因此,我认为Scala设计人员认为这个功能非常普遍,可以将其包含在标准的Scala库中。 (我同意)

亚历