【问题标题】:scala 2.8 control Exception - what is the point?scala 2.8 控制异常 - 有什么意义?
【发布时间】:2009-10-29 16:07:53
【问题描述】:

在即将发布的 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 中的异常处理看起来像其他语言(如果是,是哪一种)?

【问题讨论】:

    标签: scala exception-handling


    【解决方案1】:

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

    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&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] =&gt; 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))
    

    这里有趣的是我完全解耦了异常处理try/catchcatch部分)从资源的关闭,这是通过finally 完成的。此外,错误处理是我可以传递给函数的值。至少,它使try/catch/finally 语句的模拟变得更加容易。 :-)

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

    最后,我将对我给出的arm 的定义发表评论。这不是一个好的。特别是,我不能使用Catch 方法如toEithertoOption 将结果从R 更改为其他值,这严重降低了在其中使用Catch 的价值。不过,我不确定如何改变这一点。

    【讨论】:

    • Daniel - 一如既往的深思熟虑的答案,但您能否扩展答案以提供 2 个不同的示例来调用您的 arm 方法?我可以看到您可以提供不同的处理程序,但是您可以通过在调用站点捕获异常以命令式的方式执行此操作...
    • 所有图灵完备的语言都是等价的。您可以在没有高阶函数的情况下进行“过滤”,这更尴尬。我会试着想一个很好的例子来说明这有什么不同。
    • 作为旁注,我一开始就避免编写示例,因为实现 Closeable 的 Java API 非常糟糕。我不想经历痛苦! :-)
    • 我确实担心 scala 2.8 中有太多玩具,虽然它们是库设计师的强大助手,但如果大多数开发人员急切地使用它们将是灾难性的。 Josh Bloch 在 2008 年就 Java 语言变化的权重比进行了精彩的演讲。我认为这也适用于 Scala 中的一些特性。
    • 最佳答案,丹尼尔。我想知道是否有一个徽章可以让您回答始终如一的长篇大论...
    【解决方案2】:

    作为编写它的人,原因是组合和封装。 scala 编译器(我打赌大多数大小合适的源代码库)到处都是吞没所有异常的地方——你可以用 -Ywarn-catchs 看到这些——因为程序员懒得枚举相关的,这是可以理解的因为它很烦人。通过独立于 try 逻辑来定义、重用和组合 catch 和 finally 块,我希望降低编写合理块的障碍。

    而且,它还没有完全完成,我还在处理其他一百万个领域。如果您查看演员代码,您可以看到巨大的剪切和粘贴的多重嵌套 try/catch/finally 块的示例。我/我不愿意满足于try { catch { try { catch { try { catch ...

    我的最终计划是让 catch 获取一个实际的 PartialFunction,而不是需要一个案例语句的文字列表,这提供了一个全新的机会水平,即 try foo() catch getPF()

    【讨论】:

    • 哦,我忘了提到我的最终计划是让 catch 使用一个实际的部分函数,​​而不是需要一个 case 语句的文字列表,这提供了一个全新的机会,即尝试 foo()赶上 getPF()
    • 参加PartialFunction的状态如何?
    • 很久以前就实现了,它在 scala 2.9.1 中。
    【解决方案3】:

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

    除了比它的等价物更短之外,新的 catch() 方法还提供了一种更实用的方式来表达相同的行为。 Try ... catch 语句通常被认为是命令式风格,异常被认为是副作用。 Catching() 在这个命令式代码上盖了一层毯子,将其隐藏起来。

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

    另一种看待这个问题的方法是,如果 Scala 没有提供这个 catch() 函数。然后很可能人们会独立地“重新发明”它,这会导致代码重复,并导致更多的非标准代码。所以我认为 Scala 设计者相信这个函数足够普遍,足以保证将它包含在标准 Scala 库中。 (我同意)

    亚历克斯

    【讨论】:

    • 你能指出一个“异常被认为是副作用”的资源吗?异常(不一定会改变目标的状态)是如何产生副作用的
    • 另外,我同意。显然这是风格问题……但在我看来,使用两种完全不同的方式来实现相同的结果是非常危险的,因为它会导致维护问题。
    • 您能否举一个采用 捕获 表达式但无法设计为采用 try-catch 的方法的示例?
    猜你喜欢
    • 1970-01-01
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 2011-03-26
    相关资源
    最近更新 更多