有两种方法可以考虑异常。一种方法是将它们视为流控制:异常会改变程序的执行流程,使执行从一个地方跳到另一个地方。第二种方法是将它们视为数据:异常是有关程序执行的信息,然后可以将其用作程序其他部分的输入。
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] => 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/catch的catch部分)从资源的关闭,这是通过finally 完成的。此外,错误处理是我可以传递给函数的值。至少,它使try/catch/finally 语句的模拟变得更加容易。 :-)
另外,我可以使用or 方法组合多个Catch,这样我的代码的不同层可能会选择为不同的异常添加不同的处理程序。这确实是我的主要观点,但我找不到一个异常丰富的界面(在我查看的短暂时间内:)。
最后,我将对我给出的arm 的定义发表评论。这不是一个好的。特别是,我不能使用Catch 方法如toEither 或toOption 将结果从R 更改为其他值,这严重降低了在其中使用Catch 的价值。不过,我不确定如何改变这一点。