【问题标题】:Stuck in the State Monad卡在状态单子中
【发布时间】:2013-01-25 06:54:09
【问题描述】:

我想使用节点和唯一键的 IntMap 创建一个图形结构。这个话题已经很好地覆盖了herehere。我通过基本上将 state -> (val,state) 的函数包装在一个新类型中来理解 state monad 的工作原理,这样我们就可以为它创建一个 monad 实例。我已经阅读了很多关于这个主题的内容。我似乎仍然无法理解如何在整个程序执行过程中获得唯一(或只是增量)值。获得一系列连续的 ID 很容易,但是一旦我“runState”退出 monad,我似乎又回到了我开始跟踪当前 ID 的地方。我觉得我被困在单子里了。我考虑的另一个选择是将整个 IntMap 和当前的“下一个”ID 保留为状态,但这似乎非常“必要”和极端。 This 问题非常相似,但没有得到很多答案(或者我可能只是遗漏了一些明显的东西)。在整个程序执行过程中,利用 state monad 获取唯一 ID 的惯用方式是什么?谢谢。

【问题讨论】:

  • 实际上我越想,将整个地图封装在一个单子中的想法听起来很有吸引力。我想编写像实际值一样使用 ID(或映射键)的函数,而不必不断地“查找”。但是,现在也很晚了,我整天都在使用 C++,所以我可能没有想清楚。
  • 我认为真正的问题是为什么你觉得你必须在runState 之外做一些事情。唯一不能将runState 向外推的情况是,当您想要访问阴影的“外部”monad(例如,IO)时。正如 Gabriel 所建议的那样,此时您需要变压器。
  • @Peter,是的,这似乎是真正的问题。

标签: haskell state-monad


【解决方案1】:

假设我们要IO-化State monad。那会是什么样子?我们纯粹的 State monad 只是一个新类型:

s -> (a, s)

好吧,IO 版本在返回最终值之前可能会产生一些副作用,如下所示:

s -> IO (a, s)

这种模式很常见,它有一个名字,特别是StateT

newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }

名称末尾有一个T,因为它是一个单子Transformer。我们称m 为“基本单子”,StateT s m 为“转换后的”单子。

如果mMonad,则StateT s m 只是Monad

instance (Monad m) => Monad (StateT s m) where {- great exercise -}

然而,除此之外,所有的 monad 转换器都实现了 MonadTrans 类,定义如下:

class MonadTrans t where
    lift :: (Monad m) => m a -> t m a

instance MonadTrans (StateT s) where {- great exercise -}

如果tStateT s,那么lift 的类型专门用于:

lift :: m a -> StateT s m a

换句话说,它可以让我们“提升”基础 monad 中的一个动作,使其成为转换后的 monad 中的一个动作。

因此,对于您的具体问题,您需要 StateT (IntMap k v) IO monad,它扩展了 IO 和额外的 State。然后你可以在这个 monad 中编写你的整个程序:

main = flip runStateT (initialState :: IntMap k v) $ do
    m <- get        -- retrieve the map
    lift $ print m  -- lift an IO action
    (k, v) <- lift readLn
    put (insert k v m)

请注意,我仍然使用getput。这是因为transformers 包实现了我描述的所有概念,并将getput 的签名概括为:

get :: (Monad m) => StateT s m s
put :: (Monad m) => s -> StateT s m ()

这意味着它们会自动在StateT 内工作。 transformers 然后将State 定义为:

type State s = StateT s Identity

这意味着您可以将getput 用于StateStateT

要了解有关 monad 转换器的更多信息,我强烈推荐 Monad Transformers - Step by Step

【讨论】:

  • 一如既往,充满智慧的答案。谢谢加布里埃尔,我需要处理一下,看看对我的申请有什么影响。
  • 那是我现在阅读的完美论文。我知道我将不得不在某个时候深入研究 Monad Transformers,我只是没有意识到多久。
  • @MFlamer 是的,Haskell 就是用小块构建解决方案。如果 monad 是语言构建块(即状态、错误处理、io),那么 monad 转换器是可组合的构建块(字面意思:组装 monad 转换器是组合,其中 monad 转换器是 monad 对象之间的 monad 态射)。当您构建自己的 monad 转换器堆栈时,您基本上是在挑选和选择要合并的语言功能,不像其他语言会为您做出选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-28
  • 2020-04-20
  • 2018-04-08
  • 2019-02-21
相关资源
最近更新 更多