Где использовать `ApplicativeError` вместо` Either`? - PullRequest
2 голосов
/ 28 апреля 2020

Есть ApplicativeError[F,E] + F[A] и Either[E, A]. Обе передают сообщение о том, что функция может завершиться с ошибкой E или преуспеть с A, но я не уверен относительно другого сообщения, которое они передают клиенту о предполагаемом способе обработки ошибки:

def f1[F[_]: Applicative[F]]: F[Either[E, A]]
def f2[F[_]: ApplicativeError[F,E]]: F[A]

Я понимаю, f1 означает: клиент отвечает за обработку ошибок. Но что f2 означает для клиента о том, как обработать ошибку?

1 Ответ

4 голосов
/ 28 апреля 2020

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 в основном выражает намерение, поскольку оба "морально" равны.

...