Ответ 1
Есть два способа думать об исключениях. Один из способов - думать о них как об управлении потоком: исключение изменяет поток выполнения программы, делая выполнение перехода из одного места в другое. Второй способ - рассматривать их как данные: исключение представляет собой информацию о выполнении программы, которая затем может использоваться в качестве входных данных для других частей программы.
Парадигма try
/catch
, используемая в С++ и Java, очень похожа на первый вид (*).
Если, однако, если вы предпочитаете иметь дело с исключениями в качестве данных, вам придётся прибегнуть к коду, например, показанному. Для простого случая это довольно легко. Однако, когда дело доходит до функционального стиля, когда композиция является королем, все начинает усложняться. Вы либо должны дублировать код, либо сворачиваете свою собственную библиотеку, чтобы справиться с ней.
Поэтому на языке, который предназначен для поддержки как функционального, так и OO-стиля, не следует удивляться тому, что библиотека поддерживает обработку исключений как данных.
И обратите внимание, что есть много других возможностей, предоставляемых Exception
для обработки вещей. Вы можете, например, обработчики захвата цепей, в значительной степени в том, что частичные функции Lift chain облегчают делегирование ответственности за обработку запросов веб-страниц.
Вот один пример того, что можно сделать, поскольку автоматическое управление ресурсами в настоящее время в моде:
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)
)
Это дает вам безопасное закрытие ресурса (обратите внимание на использование игнорирования) и по-прежнему применяет любую логику catching, которую вы можете использовать.
(*) Любопытно, что контроль исключений 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))
Интересно, что я полностью отменил обработку исключений (catch
часть try
/catch
) от закрытия ресурса, который выполняется через finally
. Кроме того, обработка ошибок - это значение, которое я могу передать функции. По крайней мере, он делает насмешливое выражение try
/catch
/finally
гораздо проще.: -)
Кроме того, я могу объединить несколько catch
с помощью метода or
, так что разные уровни моего кода могут выбирать для добавления разных обработчиков для разных исключений. На самом деле это мой главный момент, но я не смог найти интерфейс, богатый исключениями (за короткое время я посмотрел:).
Я закончу с замечанием об определении arm
, которое я дал. Это нехорошо. В частности, я не могу использовать методы catch
, такие как toEither
или toOption
, чтобы изменить результат из R
на что-то еще, что серьезно уменьшает значение использования catch
в нем. Однако я не уверен, как это изменить.