【问题标题】:Understanding Monad Transformer Type Signatures了解 Monad Transformer 类型签名
【发布时间】:2019-04-06 23:53:19
【问题描述】:

我目前正在处理 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,这就是为什么它是 ReaderT r m a 中的 m,但它让我的大脑弯曲。

谁能提供任何见解,我是否正确分析了这些类型,我只需要接受s -> (s -> IO (a, s)) a 确实有效?

谢谢

【问题讨论】:

  • 我们有newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }。既然如此,(StateT s IO) a 归结为s -> IO (a,s),并且没有悬空的a
  • @duplode ReaderT 返回的a 怎么样? newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }。因为IO (a,s) 在元组中有a,所以它像我上面那样从最右边被删除了?
  • "ReaderT 返回的a 怎么样?" -- 那是(StateT s IO) a 中的a。请注意,ReaderT 本身并不返回 a,而是返回 m a
  • @duplode :mind-blast: 现在完全有道理了,哇非常感谢你帮我解决这个问题!!

标签: haskell functional-programming


【解决方案1】:

您编写的堆栈有点奇怪,因为它在左侧由 m 参数化,但在右侧专用于 IO,所以让我们看看完整的 m-parametrized 变体:

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)

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

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

当然,如果我们只对同构的类型感兴趣,那么 newtype 包装器无关紧要,所以只需将它们重写为类型别名:

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 类型的更新状态。

【讨论】:

  • 您的回答很有帮助,非常感谢,谢谢!
猜你喜欢
  • 2011-01-06
  • 1970-01-01
  • 1970-01-01
  • 2014-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多