Ответ 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
требует ее. И это не всегда то, что вы хотите, по причинам, как полезная проверка полноты.