了解Monad变压器类型签名

Sha*_*ger 7 haskell functional-programming

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

newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s IO) a }
Run Code Online (Sandbox Code Playgroud)

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

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

只是看起来不像最后一行上的有效返回类型签名,我们本质上有一个函数,该函数接受s并返回紧靠a?的函数。

知道内部函数最终会得出Monad的值,这就是为什么它是min 的原因ReaderT r m a,但是它使我的大脑弯曲。

谁能提供任何见解,我是否已经分析了类型,而我只是必须接受那s -> (s -> IO (a, s)) a确实有效?

谢谢

K. *_*uhr 8

您编写的堆栈有点奇怪,因为它的参数m设置在左侧,而专用IO于右侧,所以让我们看一下完全m参数化的变体:

newtype Stack s m a = Stack { runStack :: ReaderT s (StateT s m) a }
Run Code Online (Sandbox Code Playgroud)

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

newtype Stack s m a = Stack (ReaderT s (StateT s m) a)
Run Code Online (Sandbox Code Playgroud)

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

newtype ReaderT r1 m1 a1 = ReaderT (r1 -> m1 a1)
newtype StateT s2 m2 a2 = StateT (s2 -> m2 (a2, s2))
Run Code Online (Sandbox Code Playgroud)

当然,如果我们只对直到同构的类型感兴趣,那么新类型包装器就没有关系,因此只需将它们重写为类型别名即可:

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

现在,很容易扩展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)
Run Code Online (Sandbox Code Playgroud)

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

直观地,这Stack是从s(第一个参数)读取的,其类型为初始状态s(第二个参数),并在m(例如IO)中返回一个单子动作,该动作可以返回type的值和type a的更新状态s