В чем разница между 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 для ветвления пути кода.

Чтобы абстрагироваться от ошибки, только для этой цели я выделил следующие различия:

  1. Either может использоваться для указания описания ошибки, которое может быть показано клиенту. Try - обертка исключения с трассировкой стека, менее описательная, менее ориентированная на клиента, больше для внутреннего использования.
  2. Either позволяет нам указать тип ошибки с существующим monoid для этого типа. В результате это позволяет нам комбинировать ошибки (обычно через аппликативные эффекты). Try абстракция, за исключением, не имеет определения monoid. С Try мы должны приложить больше усилий, чтобы извлечь ошибку и обработать ее.

    Исходя из этого, вот мои лучшие практики: Когда я хочу абстрагировать эффект ошибки, я всегда использую Either в качестве первого выбора, с List/Vector/NonEmptyList в качестве типа ошибки.

    Try используется только при вызове кода, написанного в ООП. Хорошими кандидатами для Try являются методы, которые могут генерировать исключение, или методы, которые отправляют запрос во внешние системы (запросы rest/soap/database в случае, если методы возвращают необработанный результат, не заключенный в абстракции FP, например, Future, например.