了解Monad变压器类型签名

时间:2019-04-06 15:30:30

标签: haskell functional-programming

我目前正在处理monad转换器,并试图真正理解类型签名,而且我有些困惑。让我们使用以下堆栈进行讨论:

newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s IO) a }

我试图逐层检查并编写未包装的类型签名,但被卡住了:

newtype Stack s m a = Stack { 
    runStack :: ReaderT s         (StateT s IO)       a }
--              ReaderT s                  m          a
--                      s ->               m          a
--                      s ->         (StateT s IO)    a
--                            StateT  s     m         a
--                      s ->         (s -> IO (a, s)) a  

在最后一行看起来好像不是有效的返回类型签名,实际上我们有一个函数带有一个s并返回与a相对应的函数?

得到,内部函数最终求值为Monad,这就是为什么它是m中的ReaderT r m a,但是却使我的大脑弯曲了。

任何人都可以提供任何见解,我是否已对类型进行了分析,而我只需要接受s -> (s -> IO (a, s)) a确实有效?

谢谢

1 个答案:

答案 0 :(得分:8)

您编写的堆栈有点奇怪,因为它的左边是m进行了参数设置,而右边则专门针对了IO,所以让我们看一下完全m进行了参数设置的变体:

newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s m) a }

现在runStack只是一个字段名称,因此我们可以将其删除并编写等效的newtype定义:

newtype Stack s m a = Stack (ReaderT s (StateT s m) a)

我们还有以下库新类型定义,跳过了字段名称。我还使用了新鲜的变量,因此在扩展时,我们不会做一些愚蠢的事情,例如混淆两个a在不同范围内:

newtype ReaderT r1 m1 a1 = ReaderT (r1 -> m1 a1)
newtype StateT s2 m2 a2 = StateT (s2 -> m2 (a2, s2))

当然,如果我们只对直到同构的类型感兴趣,则新类型包装程序无关紧要,因此只需将它们重写为类型别名即可:

type Stack s m a = ReaderT s (StateT s m) a
type ReaderT r1 m1 a1 = r1 -> m1 a1
type StateT s2 m2 a2 = s2 -> m2 (a2, s2)

现在,轻松扩展Stack类型:

Stack s m a
= ReaderT s (StateT s m) a
-- expand ReaderT with r1=s, m1=StateT s m, a1=a
= s -> (StateT s m) a
= s -> StateT s m a
-- expand StateT with s2=s m2=m a2=a
= s -> (s -> m (a, s))
= s -> s -> m (a, s)

正如@duplode所指出的,这里没有多余的a

直观地,此Stacks(第一个参数)中读取的类型为s(第二个参数)的初始状态,并在m中返回单子动作(例如IO),它可以返回类型为a的值和类型为s的更新状态。