问题描述
我理解(我认为),Haskell中的Either
和Except
之间存在密切的关系,而且很容易从一种转换为另一种.但是我对于处理Haskell中的错误的最佳实践以及在什么情况和情况下我会选择一个而不是另一个感到有些困惑.例如,在示例<Control.Monad.Except
中提供的/a>,定义中使用Either
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的初学者,因此,当我希望后者比前者更具体的示例时,将不胜感激,尤其是为什么它如此复杂(在我看来)如此复杂.例如,使用calculateLength
版本的Except
版本的函数的调用者可以做什么,而使用Either
版本却不能(或者至少不那么容易)?
摘要
将Either
用于正常的成功/错误 API.它是在 base 库中定义的,因此不会将其他依赖项推送到使用者.另外,这是最基本的Haskell类型之一,因此每个人"都了解它的工作方式.
仅当您特别需要将Either
与另一个monad(例如IO
)结合使用时,才使用ExceptT
.此类型在 transformers 库中定义,因此对使用者具有额外的依赖性.此外,monad变压器是Haskell的更高级功能,因此您不能指望每个人都了解如何使用它.
推测原因
做出这些决定的时间并不长,但是造成这种混乱的历史原因多种多样. Haskell是一门古老的语言(比Java古老!),因此尽管已进行精简和纠正旧错误的努力,但仍然存在.据我所知,Either
/ExceptT
混乱是其中一种情况.
我推测 Either
比monad变换器的概念还老,所以我想像Either
类型早已引入到 base 库中在Haskell的历史中.
Maybe
似乎也是如此.
其他monad,例如 Reader 和 State 似乎是与它们的monad转换器一起被引入(或至少被重新精炼"的).例如,Reader
只是ReaderT
的特殊情况,其中其他" Monad
是Identity
:
type Reader r = ReaderT r Identity
StateT
也是如此:
type State s = StateT s Identity
这是在 transformers 库中定义的许多monad的通用模式. ExceptT
通过将Except
定义为ExceptT
的特例来遵循该模式.
该模式有例外.例如,MaybeT
并未将Maybe
定义为特例.再次,我认为这是出于历史原因; Maybe
大概是在很久以前,任何人都开始在 transformers 库中工作的.
关于Either
的故事似乎更加令人费解.据我所知,最初有 个EitherT
monad变压器,但显然(我忘记了细节)它的行为方式有问题(可能违反了一些法律) ),所以用另一个名为ErrorT
的变压器代替了它,但事实证明这是错误的.我想第三次是魅力,所以介绍了ExceptT
.
Control.Monad.Trans.Except
模块通过使用类型别名定义无效"的特殊情况来遵循大多数其他monad转换器的模式:
type Except e = ExceptT e Identity
我想这样做是因为它可以,但是可能很不幸,因为它令人困惑.肯定有现有技术表明monad变压器不必遵循这种模式(例如MaybeT
),所以我认为,如果模块不这样做,那就更好了,但是那是我们要去的地方.是.
我实际上会忽略Except
类型,而改用Either
,但是如果需要变压器,请使用ExceptT
.
I understand (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 provided in Control.Monad.Except
, Either
is used in the definition
type LengthMonad = Either LengthError
so that calculateLength "abc"
is
Right 3
If instead one were to define
type LengthMonad = Except LengthError
then calculateLength "abc"
would be
ExceptT (Identity (Right 3))
I'm confused about what purpose this would serve and when one one want it. Why does everything returned from calculateLength
always have Identity
; why not just SomeExceptionType (Right 3)
or even SomeSuccessType 3
?
I'm a Haskell beginner when it comes to concepts like this, so a concrete example of when I'd want the latter over the former would be much appreciated, especially why it's so (apparently to me) complex. For example, what can a caller of a function that uses the Except
version of calculateLength
do, that they can't (or at least can't as easily) do with the Either
version?
Abstract
Use Either
for normal success/error APIs. It's defined in the base library, so it doesn't push other dependencies on a consumer. Also, this is one of the most basic Haskell types, so 'everyone' understands how it works.
Only use ExceptT
if you specifically need to combine Either
with another monad (such as, for example IO
). This type is defined in the transformers library, so pushes an extra dependency on consumers. Additionally, monad transformers is a more advanced feature of Haskell, so you can't expect everyone to understand how to use it.
Speculation on reasons
I wasn't around when those decisions were made, but it seems that there are various historical reasons for the confusion. Haskell is an old language (older than Java!), so even though efforts have been made to streamline it and rectify old mistakes, some still remain. As far as I can tell, the Either
/ExceptT
confusion is one of those situations.
I'm speculating that Either
is older than the concept of monad transformers, so I imagine that the type Either
was introduced to the base library early in the history of Haskell.
The same thing seems to be the case with Maybe
.
Other monads, likes e.g. Reader and State seem to have been introduced (or at least 'retconned') together with their monad transformers. For example, Reader
is just a special case of ReaderT
, where the 'other' Monad
is Identity
:
type Reader r = ReaderT r Identity
The same goes for StateT
:
type State s = StateT s Identity
That's the general pattern for many of the monads defined in the transformers library. ExceptT
just follows the pattern by defining Except
as the special case of ExceptT
.
There are exceptions to that pattern. For example, MaybeT
doesn't define Maybe
as a special case. Again, I believe that this is for historical reasons; Maybe
was probably around long before anyone started work on the transformers library.
The story about Either
seems even more convoluted. As far as I can tell, there was, originally, an EitherT
monad transformer, but apparently (I forget the details) there was something wrong with the way that it behaved (it probably broke some laws), so it was replaced with another transformer called ErrorT
, which again turned out to be wrong. Third time's the charm, I suppose, so ExceptT
was introduced.
The Control.Monad.Trans.Except
module follows the pattern of most other monad transformers by defining the 'uneffectful' special case using a type alias:
type Except e = ExceptT e Identity
I suppose it does that because it can, but it may be unfortunate, because it's confusing. There's definitely prior art that suggests that a monad transformer doesn't have to follow that pattern (e.g. MaybeT
), so I think it would have been better if the module hadn't done that, but it does, and that's where we are.
I would essentially ignore the Except
type and use Either
instead, but use ExceptT
if a transformer is required.
这篇关于在Haskell中,"Except"的复杂性有什么作用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!