1

考虑State类型 - 或者至少是简化版本:

newtype State s a = State { runState :: s -> (a, s) }

现在,假设我们要导出StateTmonad 转换器。transformers定义如下:

newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }

在这里,m被放置在函数箭头的右侧,但在元组之外。但是,如果我们不知道正确的答案,我们可能会放在m其他地方:

newtype StateT s m a = StateT { runStateT :: m (s -> (  a,  s)) }
newtype StateT s m a = StateT { runStateT ::    s -> (m a,  s)  }

显然版本transformers是正确的,但为什么呢?更一般地说,在定义 monad 转换器时,如何知道将“内部”monad 的类型变量放在哪里?更概括地说, comonad 变压器是否有类似的规则?

4

1 回答 1

4

我认为在以下情况下可以很容易地理解差异m ~ IO

s -> IO (a, s)

是一种动作类型,它可以读取当前状态s,根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后生成新状态s和返回值a

反而:

IO (s -> (a, s))

是在不知道当前状态的情况下立即执行 IO 的动作类型。在所有的 IO 结束后,它返回一个将旧状态映射到新状态的纯函数和一个返回值。

这与之前的类型类似,因为新的状态和返回值可以同时依赖于之前的状态和 IO。但是,IO 不能依赖于当前状态:例如,不允许打印当前状态。

反而,

s -> (IO a,  s)

是读取当前状态的动作的类型s,然后根据它执行 IO(例如打印当前状态,从用户那里读取一行),然后产生一个返回值a。依赖于当前状态,bot 不在 IO 上,产生一个新状态。这种类型实际上同构于一对函数(s -> IO a, s -> s)

在这里,IO 可以从用户那里读取一行,并根据该行产生一个返回值a,但新状态不能依赖于该行。

由于第一个变体更通用,我们希望它作为我们的状态转换器。

我认为决定放置位置没有“一般规则” m:这取决于我们想要实现的目标。

于 2018-03-31T12:23:20.583 回答