ApplicativeError[F, E]
предполагает, что тип E
каким-то образом закодирован в F
, и вы можете создать экземпляр ApplicativeError[F, E]
только потому, что для этого конкретного F
имеет смысл иметь ошибку E
. Примеры:
ApplicativeError[Task, Throwable]
- Task
использует Throwable
в качестве канала ошибок, поэтому имеет смысл выставить Throwable
в качестве алгебры ошибок. Фактически Sync[F]
от Cats Effect реализует MonadError[F, Throwable]
, который в свою очередь реализует ApplicativeError[F, Throwable]
- многие классы типов Cats Effect предполагают, что вы имеете дело только с Throwable
ApplicativeError[Task[Either[E, *]], E]
- в таких комбинациях у вас будет E
как в определении типа F
, так и в определении c, а также в параметре E
- это типично для всех видов бифункторов: Task[Either[E, *]]
, EitherT[Task, E, *]
, ZIO IO[E, A]
и т. Д.
Интерфейс ApplicativeError[F, E]
не обрабатывает ошибку самостоятельно. Но он предоставляет методы:
raiseError[A](e: E): F[A]
- который создает ошибку handleErrorWith[A](fa: F[A])(f: (E) ⇒ F[A]): F[A]
- который обработал ее
, чтобы вы могли сказать это как с этим справиться.
Оба работают, не предполагая ничего о природе ошибки, и F, кроме того, что это Applicative
, который может дать сбой. Если вы используете только F
и вводите классы, вы можете использовать эти методы для восстановления после ошибки. И если вам известен точный тип F
на сайте вызовов (поскольку он жестко задан как Task
, IO
, Coeval
и т. Д. 1073 *), вы можете использовать метод восстановления напрямую.
Основное отличие состоит в том, что результат F[Either[E, A]]
не говорит вызывающей стороне, что E
следует рассматривать как сбой. F[A]
говорит о том, что может быть только A
успешное значение. Кроме того, для одного требуется Applicative
, а для другого ApplicativeError
, поэтому существует разница в «мощности», необходимой для создания значения - если вы видите ApplicativeError
, хотя в результате нет E
, вы можете предположить, что метод может завершиться с ошибкой потому что для этого требуется более мощный класс типов.
Но, конечно, он не задуман в камне и в основном предназначен для выражения намерений, потому что везде, где у вас есть F[A]
, вы можете конвертировать в F[Either[E, A]]
и обратно ApplicativeError[F, E]
* (для этого есть даже такие методы, как attempt[A](fa: F[A]): F[Either[E, A]]
или fromEither[A](x: Either[E, A]): F[A]
). Таким образом, в одной части вашего приложения вы можете иметь F[A]
с E
алгеброй, но есть одна чистая функция, которая использует Either[E, A] => B
, которую вы хотели бы использовать - тогда вы можете конвертировать, отображать и, если необходимо, конвертировать назад.
TL; DR в основном выражает намерение, поскольку оба "морально" равны.