`Except` 的复杂性在 Haskell 中有什么用?

What purpose does the complexity of `Except` serve in Haskell?

我在 (I think) that there is a close relationship between Either and Except in Haskell, and that it is easy to convert from one to the other. But I'm a bit confused about best practices for handling errors in Haskell and under what circumstances and scenarios I would choose one over the other. For example, in the example中提供了Control.Monad.ExceptEither定义中使用了

type LengthMonad = Either LengthError

所以calculateLength "abc"

Right 3

如果改为定义

type LengthMonad = Except LengthError

那么 calculateLength "abc" 就是

ExceptT (Identity (Right 3))

我对这将用于什么目的以及何时需要它感到困惑。为什么从 calculateLength 返回的所有内容总是 Identity;为什么不只是 SomeExceptionType (Right 3) 甚至 SomeSuccessType 3

我是一个 Haskell 初学者,当涉及到像这样的概念时,所以一个具体的例子,当我想要后者而不是前者时,将非常感激,特别是为什么它是如此(显然我)复杂。例如,使用 calculateLengthExcept 版本的函数的调用者可以做什么,而他们不能(或至少不能那么容易地)使用 Either 做什么版本?

摘要

对正常的 success/error API 使用 Either。它在 base 库中定义,因此它不会将其他依赖项推给消费者。此外,这是最基本的 Haskell 类型之一,因此 'everyone' 了解它的工作原理。

如果您特别需要将 Either 与另一个单子组合(例如 IO),则仅使用 ExceptT。这种类型在 transformers 库中定义,因此对消费者产生了额外的依赖。此外,monad transformers 是 Haskell 的一个更高级的特性,所以你不能指望每个人都能理解如何使用它。

原因推测

做出这些决定时我不在,但是 it seems that there are various historical reasons for the confusion。 Haskell 是一种 旧的 语言(比 Java 更早!),因此尽管已努力简化它并纠正旧错误,但仍有一些错误。据我所知,Either/ExceptT 混乱就是其中一种情况。

推测 Either 比 monad 转换器的概念更早,所以我想 Either 类型被引入 base 库的历史早于 Haskell.

Maybe似乎也是如此。

其他单子,例如ReaderState 似乎与它们的 monad 转换器一起被引入(或至少 'retconned')。例如,Reader 只是 ReaderT 特例 ,其中 'other' MonadIdentity

type Reader r = ReaderT r Identity

StateT也一样:

type State s = StateT s Identity

这是 transformers 库中定义的许多单子的通用模式。 ExceptT 只是通过将 Except 定义为 ExceptT.

的特例来遵循模式

这种模式也有例外。例如,MaybeT 没有将 Maybe 定义为特例。同样,我认为这是出于历史原因; Maybe 可能早在有人开始研究 transformers 库之前就存在了。

关于 Either 的故事似乎更加复杂。据我所知,,最初是一个 EitherT monad 转换器,但显然(我忘记了细节)它的行为方式有问题(它可能违反了一些法律),因此它被另一个名为 ErrorT 的转换器所取代,这又被证明是错误的。第三次是魅力,我想,所以 ExceptT 被介绍了。

Control.Monad.Trans.Except 模块通过使用类型别名定义 'uneffectful' 特殊情况来遵循大多数其他 monad 转换器的模式:

type Except e = ExceptT e Identity

我想它这样做是因为它可以,但它可能很不幸,因为它令人困惑。肯定有现有技术表明 monad 转换器不必遵循该模式(例如 MaybeT),所以我认为如果模块没有这样做会更好,但它确实如此,那就是我们在哪里。

我基本上会忽略 Except 类型并使用 Either 代替,但如果需要转换器,请使用 ExceptT