Есть два способа думать об исключениях. Один из способов - думать о них как об управлении потоком: исключение изменяет поток выполнения программы, заставляя выполнение переходить из одного места в другое. Второй способ - рассматривать их как данные. Исключением является информация о выполнении программы, которую затем можно использовать в качестве входных данных для других частей программы.
Парадигма try
/ catch
, используемая в C ++ и Java, в значительной степени относится к первому виду (*).
Если, однако, если вы предпочитаете работать с исключениями в качестве данных, вам придется прибегнуть к коду, подобному показанному. Для простого случая это довольно просто. Однако, когда дело доходит до функционального стиля, где король композиции, вещи начинают усложняться. Вы либо должны дублировать код, либо катите свою собственную библиотеку, чтобы справиться с этим.
Следовательно, на языке, который подразумевает поддержку как функционального, так и ОО-стиля, не следует удивляться, увидев поддержку библиотеки для обработки исключений как данных.
И обратите внимание, что 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)
)
Что дает вам безопасное закрытие ресурса (обратите внимание на использование игнорирования) и все еще применяет любую логику перехвата, которую вы можете захотеть использовать.
(*) Любопытно, что исключительный контроль Форта, catch
& throw
, представляет собой их смесь. Поток переходит с throw
на catch
, но затем эта информация обрабатывается как данные.
EDIT
Хорошо, хорошо, я уступаю. Я приведу пример. ОДИН пример и все тут! Я надеюсь, что это не слишком надумано, но нет никакого способа обойти это. Подобные вещи были бы наиболее полезны в больших рамках, а не в небольших выборках.
Во всяком случае, давайте сначала определим, что делать с ресурсом. Я решил напечатать строки и вернуть количество напечатанных строк, а вот код:
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
в нем. Хотя я не уверен, как это изменить.