首页 > 解决方案 > `Except` 的复杂性在 Haskell 中的作用是什么?

问题描述

理解(我认为)Haskell 之间有密切的关系,Either并且Except很容易从一个转换到另一个。但是我对在 Haskell 中处理错误的最佳实践以及在什么情况和场景下我会选择其中一个感到有点困惑。例如,在 中提供的示例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 初学者,所以我希望后者而不是前者的具体例子将不胜感激,特别是为什么它如此(显然对我而言)复杂。例如,使用doExcept版本的函数的调用者可以calculateLength做什么,而他们不能(或至少不能那么容易)用该Either版本做什么?

标签: haskellerror-handlingexception-handlingmonadsmonad-transformers

解决方案


抽象的

用于Either正常的成功/错误API。它是在基础库中定义的,因此它不会将其他依赖项推向消费者。此外,这是最基本的 Haskell 类型之一,因此“每个人”都了解它的工作原理。

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

推测原因

做出这些决定时我不在,但似乎有各种历史原因造成了这种混乱。Haskell 是一门古老的语言(比 Java 还要古老!),因此尽管已经努力简化它并纠正旧的错误,但仍然存在一些错误。据我所知,Either/ExceptT混乱就是其中一种情况。

推测Either比 monad 转换器的概念更早,所以我想这种类型是在 Haskell 历史早期Either被引入基础库的。

同样的事情似乎也是如此Maybe

其他单子,例如ReaderState似乎已经与它们的单子转换器一起被引入(或至少“重新连接”)。例如,Reader只是 的一个特例ReaderT其中“其他”MonadIdentity

type Reader r = ReaderT r Identity

这同样适用于StateT

type State s = StateT s Identity

这是在转换器库中定义的许多 monad 的一般模式。只是通过定义为 的特例ExceptT来遵循模式。ExceptExceptT

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

关于的故事Either似乎更加扑朔迷离。据我所知,最初有一个monadEitherT变压器,但显然(我忘记了细节)它的行为方式有问题(它可能违反了一些法律),所以它被另一个名为ErrorT,这再次证明是错误的。我想第三次是魅力,所以ExceptT被介绍了。

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

type Except e = ExceptT e Identity

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

我基本上会忽略Except类型并Either改为使用,但ExceptT如果需要变压器则使用。


推荐阅读