В чем разница между Try and Either?
Согласно документации:
Тип Try представляет собой вычисление, которое может либо привести к исключение или вернуть успешно вычисленное значение. Он похож на, но семантически отличается от scala.util.Either type.
Документы не вдаются в подробности о семантической разности. Оба, похоже, способны сообщать о успехах и неудачах. Почему вы используете один над другим?
Ответы
Ответ 1
Я рассмотрел связь между Try
, Either
и Option
в этом ответе. Ниже приведены основные моменты относительно отношения между Try
и Either
:
Try[A]
изоморфно Either[Throwable, A]
. Другими словами, вы можете обрабатывать Try
как Either
с левым типом Throwable
, и вы можете рассматривать любой Either
, который имеет левый тип Throwable
как Try
. Традиционно использовать Left
для отказов и Right
для успеха.
Конечно, вы также можете использовать Either
более широко, не только в ситуациях с отсутствующими или исключительными значениями. Существуют и другие ситуации, когда Either
может помочь выразить семантику простого типа объединения (где значение является одним из двух типов).
Семантически, вы можете использовать Try
, чтобы указать, что операция может завершиться неудачно. Аналогичным образом вы можете использовать Either
в такой ситуации, особенно если ваш тип ошибки является чем-то иным, чем Throwable
(например, Either[ErrorType, SuccessType]
). И тогда вы также можете использовать Either
, когда вы работаете над типом объединения (например, Either[PossibleType1, PossibleType2]
).
Стандартная библиотека не включает преобразования от Either
до Try
или от Try
до Either
, но довольно просто обогатить Try
и Either
при необходимости:
object TryEitherConversions {
implicit class EitherToTry[L <: Throwable, R](val e: Either[L, R]) extends AnyVal {
def toTry: Try[R] = e.fold(Failure(_), Success(_))
}
implicit class TryToEither[T](val t: Try[T]) extends AnyVal {
def toEither: Either[Throwable, T] = t.map(Right(_)).recover(PartialFunction(Left(_))).get
}
}
Это позволит вам сделать:
import TryEitherConversions._
//Try to Either
Try(1).toEither //Either[Throwable, Int] = Right(1)
Try("foo".toInt).toEither //Either[Throwable, Int] = Left(java.lang.NumberFormatException)
//Either to Try
Right[Throwable, Int](1).toTry //Success(1)
Left[Throwable, Int](new Exception).toTry //Failure(java.lang.Exception)
Ответ 2
Чтобы ответить на ваш вопрос: "Что такое семантическая разница":
Это, вероятно, относится к flatMap и map, которые отсутствуют в Листе и либо распространяют отказ, либо сопоставляют значение успеха в Try. Это позволяет, например, связывать как
for {
a <- Try {something}
b <- Try {somethingElse(a)}
c <- Try {theOtherThing(b)}
} yield c
который делает именно то, что вы надеетесь, возвращает Try, содержащий либо первое исключение, либо результат.
У Try есть много других полезных методов и, конечно же, его метод сопутствующего применения, который делает его очень удобным для его предполагаемого использования - обработку исключений.
Если вы действительно хотите быть перегружены, есть два других класса, которые могут представлять интерес для такого приложения. Scalaz имеет класс, называемый \/ "( ранее известный как принц), произносится как" Либо ", который в основном похож на Либо, но flatMap и карта работают с правильным значением. Аналогично, а не Scalactic имеет" Or ", который также похож на Либо, но flatMap и карта работают с левым значением.
Я не рекомендую Scalaz для начинающих.
Ответ 3
Either
не означает успех и неудачу, это просто контейнер для A или B. Обычно используется для представления успехов и сбоев, причем конвенция заключается в том, чтобы поместить сбой с левой стороны, и успех справа.
A Try
можно рассматривать как Either
с левым типом, установленным на Throwable
. Try[A]
будет эквивалентен Either[Throwable, A]
.
Используйте Try
, чтобы четко определить потенциальный сбой в вычислении, а отказ - исключением. Если вы хотите представить ошибку с другим типом (например, String или набор классов case, расширяющих запечатанный признак, например), используйте Either
.
Ответ 4
Either
является более общим, так как он просто представляет собой несвязные объединения типов.
В частности, он может представлять объединение допустимых возвращаемых значений некоторого типа X
и Exception
. Однако он не пытается самостоятельно перехватывать какие-либо исключения. Вы должны добавить блоки try-catch вокруг опасного кода, а затем убедитесь, что каждая ветка возвращает соответствующий подкласс Either
(обычно: Left
для ошибок, Right
для успешных вычислений).
Try[X]
можно рассматривать как Either[Exception, X]
, но он также самостоятельно ловит Исключения.
Ответ 5
Either[X, Y]
использование более общее. Поскольку его имя говорит, что оно может представлять либо объект типа X, либо Y.
Try[X]
имеет только один тип, и это может быть либо успех [X], либо отказ (который содержит Throwable
).
В какой-то момент вы можете увидеть Try[X]
как Either[Throwable,X]
Что нравится в Try[X]
, так это то, что вы можете связать с ним дополнительные операции, если это действительно успех, который они выполнили, если это был отказ, он не будет
val connection = Try(factory.open())
val data = connection.flatMap(conn => Try(conn.readData()))
//At some point you can do
data matches {
Success(data) => print data
Failure(throwable) => log error
}
Конечно, вы всегда можете использовать oneline это как
Try(factory.open()).flatMap(conn => Try(conn.readData()) matches {
Success(data) => print data
Failure(throwable) => log error
}
Ответ 6
Как уже упоминалось, Either
является более общим, поэтому он может не только переносить ошибку/успешный результат, но также может использоваться в качестве альтернативы Option
для ветвления пути кода.
Чтобы абстрагироваться от ошибки, только для этой цели я выделил следующие различия:
Either
может использоваться для указания описания ошибки, которое может быть показано клиенту. Try
- обертка исключения с трассировкой стека, менее описательная, менее ориентированная на клиента, больше для внутреннего использования.
Either
позволяет нам указать тип ошибки с существующим monoid
для этого типа. В результате это позволяет нам комбинировать ошибки (обычно через аппликативные эффекты). Try
абстракция, за исключением, не имеет определения monoid
. С Try
мы должны приложить больше усилий, чтобы извлечь ошибку и обработать ее.
Исходя из этого, вот мои лучшие практики:
Когда я хочу абстрагировать эффект ошибки, я всегда использую Either
в качестве первого выбора, с List
/Vector
/NonEmptyList
в качестве типа ошибки.
Try
используется только при вызове кода, написанного в ООП. Хорошими кандидатами для Try
являются методы, которые могут генерировать исключение, или методы, которые отправляют запрос во внешние системы (запросы rest/soap/database в случае, если методы возвращают необработанный результат, не заключенный в абстракции FP, например, Future
, например.