有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
对客户端意味着如何处理错误?
ApplicativeError[F, E]
假定E
的类型以某种方式编码在F
中,并且您只能创建ApplicativeError[F, E]
的实例,因为对于此特定的F
,有意义的是错误E
。范例:
ApplicativeError[Task, Throwable]
-Task
使用Throwable
作为错误通道,因此将Throwable
公开为错误代数是有意义的。由于Cats Effect的Sync[F]
实现了MonadError[F, Throwable]
,而Cs又实现了ApplicativeError[F, Throwable]
-Cats Effect的许多类型类都假设您只处理Throwable
ApplicativeError[Task[Either[E, *]], E]
-在这种组合中,您会在E
类型的特定定义以及F
参数中都具有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
等),则可以直接使用恢复方法。
主要区别是结果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,主要是表达意图,因为两者在道德上是平等的。