被困在州Monad

时间:2013-01-25 06:54:10

标签: haskell state-monad

我想使用节点和唯一键的IntMap创建图形结构。该主题已涵盖herehere。我理解状态monad是如何通过基本包装状态函数 - > (val,state)在newtype中,所以我们可以为它创建一个monad实例。我已经阅读了很多相关主题。我仍然无法理解如何在程序执行过程中获得唯一(或仅增量)值。获得一系列连续的ID很容易,但是一旦我“runState”退出monad,似乎我回到了我开始跟踪当前ID的位置。我觉得我被卡在了monad中。我考虑的另一个选择是保持整个IntMap和当前“下一个”ID作为状态,但这似乎非常“必要”和极端。 This问题非常相似,但没有得到很多答案(或者我可能只是错过了一些明显的答案)。在程序执行过程中利用状态monad获取唯一ID的惯用方法是什么?感谢。

1 个答案:

答案 0 :(得分:11)

让我们想象一下IO - {if} 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,因为它是一个monad T变换器。我们将m称为“基础monad”,将StateT s m称为“已转换的”monad。

如果StateT s mMonad,则

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