我目前正在处理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
确实有效?
谢谢
答案 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
。
直观地,此Stack
从s
(第一个参数)中读取的类型为s
(第二个参数)的初始状态,并在m
中返回单子动作(例如IO
),它可以返回类型为a
的值和类型为s
的更新状态。