Scala 2.8 Исключение контроля - какой смысл? - PullRequest
21 голосов
/ 29 октября 2009

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

Ответы [ 3 ]

28 голосов
/ 29 октября 2009

Есть два способа думать об исключениях. Один из способов - думать о них как об управлении потоком: исключение изменяет поток выполнения программы, заставляя выполнение переходить из одного места в другое. Второй способ - рассматривать их как данные. Исключением является информация о выполнении программы, которую затем можно использовать в качестве входных данных для других частей программы.

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

9 голосов
/ 31 октября 2009

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

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

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

3 голосов
/ 29 октября 2009

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

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

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

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

Alex

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...