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

Почему `scala.util.Try` не упоминается в главе "Обработка ошибок без исключений" в книге "функциональное программирование в Scala"?

В главе "Обработка ошибок без исключений" в книге "функциональное программирование в Scala" автор дает:

  • Проблема бросания исключений из тела функции
  • Используйте Option, если мы не заботимся о фактическом исключении
  • Используйте Either, если мы заботимся о фактическом исключении

Но scala.util.Try не упоминается. С моей точки зрения, я думаю, что Try очень подходит, когда мы заботимся об истинном исключении, почему он не упоминается? Есть ли причина, по которой я пропустил?

4b9b3361

Ответ 1

Я не являюсь автором функционального программирования в Scala, но я могу сделать несколько догадок о том, почему они не упоминают Try.

Некоторым людям не нравится стандартная библиотека Try, потому что они требуют что нарушает закон композиции исполнителя. Я лично считаю, что эта позиция глупа, по причинам, которые Джош Суэрет упоминает в комментариях SI-6284, но в дебатах подчеркивается важный аспект дизайна Try.

Try map и flatMap явно предназначены для работы с функциями, которые могут генерировать исключения. Люди из школы мысли FPiS (включая меня) склонны предлагать обертывать такие функции (если вам абсолютно необходимо иметь дело с ними вообще) в безопасных версиях на низком уровне в вашей программе, а затем выставлять API, который никогда не будет бросать (нефатальные) исключения.

Включение Try в ваш API путает слои в этой модели - вы гарантируете, что ваши методы API не будут генерировать исключения, но тогда вы передаете людям тип, который предназначен для использования с функциями, которые бросают исключения.

Это только жалоба на стандартную разработку библиотеки и реализацию Try. Достаточно легко представить версию Try с другой семантикой, где методы map и flatMap не учитывали исключения, и все равно были бы веские причины избегать этой "улучшенной" версии Try всякий раз возможно.

Одна из этих причин заключается в том, что использование Either[MyExceptionType, A] вместо Try[A] позволяет получить больше пробега из проверки полноты компилятора. Предположим, что я использую следующий простой ADT для ошибок в моем приложении:

sealed class FooAppError(message: String) extends Exception(message)

case class InvalidInput(message: String) extends FooAppError(message)
case class MissingField(fieldName: String) extends FooAppError(
  s"$fieldName field is missing"
)

Теперь я пытаюсь решить, должен ли метод, который может только сбой одним из этих двух способов, возвращать Either[FooAppError, A] или Try[A]. Выбор Try[A] означает, что мы отбрасываем информацию, потенциально полезную как для пользователей, так и для компилятора. Предположим, что я пишу такой метод:

def doSomething(result: Either[FooAppError, String]) = result match {
  case Right(x) => x
  case Left(MissingField(_)) => "bad"
}

Я получу хорошее предупреждение о компиляции, сообщая мне, что матч не является исчерпывающим. Если я добавлю кейс для отсутствующей ошибки, предупреждение исчезнет.

Если бы я использовал Try[String] вместо этого, я бы также получил исчерпывающую проверку, но единственный способ избавиться от предупреждения - это сделать все, что угодно - просто невозможно перечислить все Throwable в совпадении с шаблоном.

Иногда нам практически не удается ограничить способы, с которыми операция может не справиться с нашим собственным типом отказа (например, FooAppError выше), и в этих случаях мы всегда можем использовать Either[Throwable, A]. Scalaз Task, например, по существу является оберткой для Future[Throwable \/ A]. Разница в том, что Either (или \/) поддерживает такую ​​подпись, а Try требует ее. И это не всегда то, что вы хотите, по причинам, как полезная проверка полноты.