docs for Control.Monad.Trans.Error
提供了组合两个monad的示例:
type ErrorWithIO e a = ErrorT e IO a
==> ErrorT (IO (Either e a))
我发现这有违反直觉:即使ErrorT
被认为包装 IO
,看起来错误信息已经注入 IO动作的结果类型。我希望它是
==> ErrorT (Either e (IO a))
基于“wrap”一词的通常含义。
为了让事情更加混乱,StateT
分别做了一些事情:
type MyError e = ErrorT e Identity -- (see footnote)
type StateWithError s e a = StateT s (MyError e) a
==> StateT (s -> ErrorT (Either e (a, s)))
状态类型s
已注入Either
的{{1}}方面,但整个Right
也已包含在函数中。
如果将monad与其他方式相结合,那么即使更多也会让人感到困惑:
Either
“外部”仍然是一个功能;它不会产生type ErrorWithState e s a = ErrorT e (State s) a
==> ErrorT (StateT (s -> (Either e a, s)))
之类的东西,其中状态函数嵌套在错误类型中。
我确信所有这些都有一些潜在的逻辑一致性,但我不太明白。因此,我发现很难想出将一个monad与另一个monad结合起来意味着什么,即使我毫不费力地理解每个monad单独的含义。
有人可以启发我吗?
(脚注:我正在使用Either e (s -> (a, s))
撰写ErrorT
,以便Identity
和StateWithError
相互一致,仅供说明之用通常我会使用ErrorWithState
并放弃StateWithError s e a = StateT s (Either e) a
图层。
答案 0 :(得分:18)
我发现这违反直觉:即使ErrorT被认为是包装IO,看起来错误信息已经被注入到IO动作的结果类型中。
Monad变形金刚通常不会“包裹”他们应用的monad,至少在任何明显的意义上都没有。将它视为“包装”将意味着仿函数构成在我的脑海中,这是具体的不在这里。
为了说明,展开定义的State s
和Maybe
的仿函数合成将如下所示:
newtype StateMaybe s a = StateMaybe (s -> (Maybe a, s)) -- == State s (Maybe a)
newtype MaybeState s a = MaybeState (Maybe (s -> (a, s))) -- == Maybe (State s a)
请注意,在第一种情况下,State
表现正常,而Nothing
不会影响州值;在第二种情况下,我们要么具有普通的State
功能,要么根本没有任何功能。在两种情况下,两个monad的特征行为实际上组合。这应该不足为奇,因为毕竟这些只是使用一个monad作为另一个monad中常用值的值而得到的。
将此与StateT s Maybe
:
newtype StateTMaybe s a = StateTMaybe (s -> Maybe (a, s))
在这种情况下,两者编织在一起;事情以State
的正常方式进行,除非我们点击Nothing
,在这种情况下计算被中止。这与上述案例有根本的不同,这就是 monad变形金刚甚至首先存在的原因 - 天真地构成它们并不需要任何特殊的机器,因为它们彼此独立运行。
至于理解哪一个在“外部”上,可能有助于将“外部”变换器视为行为在某种意义上处理“优先级”时的行为。 monad,而“内在”monad只看到常规业务。请注意,这就是IO
始终处于最内层的原因 - 它不会让任何其他东西在其业务中起作用,而假设的IOT
变换器将被迫允许包裹的monad拉出所有类型的恶作剧,比如复制或丢弃RealWorld
令牌。
StateT
和ReaderT
都将“内部”monad放在函数的结果周围;在获得改造后的monad之前,你必须提供一个州的价值或环境。
MaybeT
和ErrorT
都在里面转换为转换后的monad,确保它可以按照通常的方式运行,除了可能不存在的值
Writer
完全是被动的,只是将自己附加到monad中的值,因为它根本不会影响行为。
ContT
通过仅包装结果类型来完成对完全变换的monad的处理。
这有点手工波浪,但是呃,monad变形金刚有点特别且开始时很困惑,唉。我不知道对于所做出的特定选择是否有任何整洁的理论依据,除了他们工作这一事实,并做你通常想要的两个monad的组合(不是组合)要做。
因此,我发现很难想到将一个monad与另一个monad结合起来意味着什么,即使我毫不费力地理解每个monad单独的含义。
是的,这听起来像是期待什么,我害怕。
答案 1 :(得分:5)
如果以您想象的方式定义ErrorT,请考虑会发生什么。你如何编码IO动作,哪个失败?使用Either e (IO a)
时,如果操作失败,则无法提供Left
值,因为当您达到操作时,已经清楚它是Right
值 - 否则它不会是动作。
但是IO (Either e a)
却不是这样。现在整个事情都是一个IO动作,可以返回Left
值来表示错误。正如其他人所指出的那样,不要将monad变形金刚视为包装。而是将它们视为功能。他们拿一个monad并把它变成另一个monad。他们转换 monads。