试图理解monad变换器产生的类型

Wyz*_*a-- 15 monads haskell monad-transformers

提供这个组合两个monad的例子的文档Control.Monad.Trans.Error:

type ErrorWithIO e a = ErrorT e IO a
==> ErrorT (IO (Either e a))
Run Code Online (Sandbox Code Playgroud)

我觉得这是违反直觉的:即使ErrorT是所谓包装 IO,它看起来像该错误的信息已经注入了IO操作的结果类型.我会期待它

==> ErrorT (Either e (IO a))
Run Code Online (Sandbox Code Playgroud)

基于"包装"一词的通常含义.

为了让事情更加混乱,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)))
Run Code Online (Sandbox Code Playgroud)

状态类型s已被注入EitherRight侧,但整个Either也被包裹在一个功能.

为了使事情变得更加混乱,如果单子相结合的其他方式:

type ErrorWithState e s a = ErrorT e (State s) a
==> ErrorT (StateT (s -> (Either e a, s)))
Run Code Online (Sandbox Code Playgroud)

"外面"仍然是一种功能; 它不会产生类似Either e (s -> (a, s))的状态,其中状态函数嵌套在错误类型中.

我确信所有这些都有一些潜在的逻辑一致性,但我不太明白.因此,我发现很难想到将一个monad与另一个monad结合起来意味着什么,即使我毫不费力地理解每个monad单独的含义.

有人可以开导我吗?


(附注:我正在写ErrorTIdentityStateWithErrorErrorWithState一致互相,用于说明目的一般情况下我只是用,StateWithError s e a = StateT s (Either e) a并放弃该ErrorT层.

C. *_*ann 18

我发现这违反直觉:即使ErrorT被认为是包装IO,看起来错误信息已经被注入到IO动作的结果类型中.

Monad变形金刚通常不会"包裹"他们应用的monad,至少在任何明显的意义上都没有.将它视为"包装"将意味着构造函数构成在我的脑海中,这具体是在这里没有发生的事情.

为了说明,forctor的functor组合State sMaybe扩展定义看起来像这样:

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)
Run Code Online (Sandbox Code Playgroud)

注意,在第一种情况下,State行为正常,并且Nothing不影响状态值; 在第二种情况下,我们要么具有普通State功能,要么根本没有功能.在两种情况下,两个单子的特征行为实际上都是组合的.因为,毕竟,这些都是一样的,你会简单地使用一个单子的其他内使用常规值有值会得到什么这并不奇怪.

比较这个StateT s Maybe:

newtype StateTMaybe s a = StateTMaybe (s -> Maybe (a, s))
Run Code Online (Sandbox Code Playgroud)

在这种情况下,两者编织在一起; 事情以正常方式进行State,除非我们点击Nothing,在这种情况下计算被中止.这与上述案例有根本的不同,这就是为什么monad变压器甚至首先存在 - 天真地组成它们并不需要任何特殊的机器,因为它们彼此独立运行.


至于理解哪一个在"外部",可能有助于将"外部"变换器视为行为在某种意义上在处理monad中的值时具有"优先级"的变换器,而"内在"的monad只能像往常一样看待生意.请注意,这就是为什么IO总是最里面的 - 它不会让任何其他东西在其业务中起来,而假设的IOT变换器将被迫允许被包裹的monad拉出各种恶作剧,如复制或丢弃RealWorld令牌.

  • StateT并且ReaderT都将"内部"monad放在函数的结果周围; 你必须在变换monad之前提供状态值或环境.

  • MaybeTErrorT两个滑本身内部转化单子,确保它可以以通常的方式表现不同的是,可能不存在的值.

  • Writer 是完全被动的,只是依附于monad中的值,因为它根本不会影响行为.

  • ContT保持事物本身,通过仅包装结果类型推迟处理转换后的monad .

这有点像手工波浪,但是呃,monad变形金刚有点特别容易让人感到困惑,唉.我不知道对于所做出的特定选择是否有任何理论上的合理理由,除了它们有效的事实,并且做你通常想要的两个monad的组合(不是组合).

因此,我发现很难想到将一个monad与另一个monad结合起来意味着什么,即使我毫不费力地理解每个monad单独的含义.

是的,这听起来像是期待什么,我害怕.


小智 5

如果按照您的想象定义ErrorT,请考虑会发生什么.你如何编码IO动作,哪个失败?随着Either e (IO a)你不能给出一个Left值,当这个动作失败了,因为当时你到达动作是已经很清楚,这是一个Right值-否则它不会是一个动作.

随着IO (Either e a)然而,这并非如此.整个事情现在是一个IO动作,可以返回一个Left值来表示错误.正如其他人所指出的那样,不要将monad变形金刚视为包装.而是将它们视为功能.他们拿一个monad并把它变成另一个monad.他们改变了 monad.