变形金刚中的类型变量位置

Type Variable Location in Transformers

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

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

现在,假设我们要导出 StateT monad 转换器。 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 转换器时,如何知道将 'inner' monad 的类型变量放在哪里?更概括地说,comonad transformers是否有类似的规则?

我认为 m ~ IO:

时的差异很容易理解
s -> IO (a, s)

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

改为:

IO (s -> (a, s))

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

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

相反,

s -> (IO a,  s)

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

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

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

我认为没有 "general rule" 来决定放在哪里 m:这取决于我们想要实现的目标。