docs for Control.Monad.Trans.Error
提供这个组合两个 monad 的例子:
type ErrorWithIO e a = ErrorT e IO a
==> ErrorT (IO (Either e a))
我觉得这违反直觉:即使
ErrorT
应该是包装 IO
,看起来错误信息已被注入(inject)到 IO 操作的结果类型中。我原以为会是==> ErrorT (Either e (IO a))
基于“包装”一词的通常含义。
为了让事情更困惑,
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
已注入(inject)Either
的Right
侧,但整体Either
也被包裹在一个函数中。更令人困惑的是,如果将 monad 反过来组合:
type ErrorWithState e s a = ErrorT e (State s) a
==> ErrorT (StateT (s -> (Either e a, s)))
“外部”仍然是一个函数;它不会产生类似
Either e (s -> (a, s))
的东西,其中状态函数嵌套在错误类型中。我确信这一切都有一些潜在的逻辑一致性,但我不太明白。因此,我发现很难思考将一个 monad 与另一个结合起来意味着什么,即使我可以毫无困难地理解每个 monad 各自的含义。
有人可以启发我吗?
( 脚注: 我将
ErrorT
与 Identity
组合在一起,以便 StateWithError
和 ErrorWithState
彼此一致,以进行说明。通常我会使用 StateWithError s e a = StateT s (Either e) a
并放弃ErrorT
层。
最佳答案
I find this counterintuitive: even though ErrorT is supposedly wrapping IO, it looks like the error information has been injected into the IO action's result type.
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
transformer 将被迫允许被包裹的 monad 进行各种恶作剧,例如复制或丢弃 RealWorld
token 。StateT
和 ReaderT
两者都将“内部”单子(monad)放在函数的结果周围;在获得转换后的 monad 之前,您必须提供状态值或环境。 MaybeT
和 ErrorT
两者都将自己滑入转换后的 monad,确保它可以以通常的方式运行,除了可能不存在的值。 Writer
是完全被动的,只是依附于 monad 中的值,因为它根本不影响行为。 ContT
将事情留给自己,通过仅包装结果类型来完全推迟处理转换后的 monad。 这有点手忙脚乱,但是,单子(monad)转换器是一种临时的,一开始就令人困惑,唉。我不知道做出的特定选择是否有任何合理的理论依据,除了它们起作用的事实,并且做你通常希望两个单子(monad)的组合(而不是组合)做的事情。
Consequently I find it difficult to think about what it means to combine one monad with another, even when I have no trouble understanding what each monad means individually.
是的,这听起来像是预期的结果,我害怕。
关于haskell - 试图了解单子(monad)变压器产生的类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7088085/