haskell - 试图了解单子(monad)变压器产生的类型

标签 haskell monads monad-transformers

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)EitherRight侧,但整体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 各自的含义。

有人可以启发我吗?

( 脚注: 我将 ErrorTIdentity 组合在一起,以便 StateWithErrorErrorWithState 彼此一致,以进行说明。通常我会使用 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 。
  • StateTReaderT两者都将“内部”单子(monad)放在函数的结果周围;在获得转换后的 monad 之前,您必须提供状态值或环境。
  • MaybeTErrorT两者都将自己滑入转换后的 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/

    相关文章:

    haskell - 在 yesod 应用程序中存储 API key 和其他 'secrets' 的位置

    scala - 使用 Scala-cats 隐式解析

    haskell - Haskell中带有currying的多输入一元函数

    haskell - 在链的末尾惯用地返回一个 Maybe

    Haskell:不在范围内:foldl'?

    haskell - 相当于 Dual for Applicative 的地方在哪里?

    haskell - 应用程序组成,单子(monad)不组成

    haskell - 仅在 monad 转换器中更新外部 monad

    scala - 在scalaz中将状态分层

    json - 为Text.JSON的Result类型编写一个liftIO实例