Подтвердить что ты не робот

Scala 2.8. Исключение - в чем смысл?

В предстоящем scala 2.8 добавлен пакет util.control, который включает в себя библиотеку break и конструкцию для обработки исключений, чтобы код выглядел следующим образом:

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 похожим на какой-то другой язык (и если да, какой)?

4b9b3361

Ответ 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 в нем. Однако я не уверен, как это изменить.

Ответ 2

Как парень, который написал это, причины - это состав и инкапсуляция. Компилятор scala (и я делаю ставку на наиболее подходящие исходные базы) усеяны местами, которые проглатывают все исключения - вы можете видеть их с помощью -Ywarn-catch, потому что программист слишком ленив, чтобы перечислять соответствующие, которые понятно, потому что это раздражает. Предоставляя возможность определять, повторно использовать и составлять улов и, наконец, блокировать независимо от логики try, я надеялся снизить барьер для записи чувствительных блоков.

И это не совсем закончено, и я тоже работаю над миллионами других областей. Если вы просматриваете код участников, вы можете увидеть примеры огромных вырезанных и вставленных многократно вложенных блоков try/catch/finally. Я/не хотел соглашаться на try { catch { try { catch { try { catch ...

Мой возможный план состоит в том, чтобы улов принял фактический PartialFunction, а не требовал литерального списка операторов case, который представляет совершенно новый уровень возможностей, т.е. try foo() catch getPF()

Ответ 3

Я бы сказал, что это прежде всего вопрос стиля выражения.

Помимо того, что новый метод catching() меньше его эквивалента, он предлагает более функциональный способ выразить одно и то же поведение. Попробуйте... заявления catch обычно считаются императивным стилем, а исключения считаются побочными эффектами. Catching() помещает одеяло в этот императивный код, чтобы скрыть его от представления.

Что еще более важно, теперь, когда у нас есть функция, тогда его легче скомпоновать с другими вещами; его можно передать другим функциям более высокого порядка, чтобы создать более сложное поведение. (Вы не можете передать инструкцию try.. catch с параметризованным типом исключений напрямую).

Еще один способ взглянуть на это, если Scala не предлагал эту функцию catching(). Тогда, скорее всего, люди будут "изобретать" его самостоятельно, и это приведет к дублированию кода и приведет к большему нестандартному коду. Поэтому я думаю, что дизайнеры Scala считают, что эта функция достаточно распространена, чтобы гарантировать ее включение в стандартную библиотеку Scala. (И я согласен)

Alex