我想使用节点和唯一键的IntMap创建图形结构。该主题已涵盖here和here。我理解状态monad是如何通过基本包装状态函数 - > (val,state)在newtype中,所以我们可以为它创建一个monad实例。我已经阅读了很多相关主题。我仍然无法理解如何在程序执行过程中获得唯一(或仅增量)值。获得一系列连续的ID很容易,但是一旦我“runState”退出monad,似乎我回到了我开始跟踪当前ID的位置。我觉得我被卡在了monad中。我考虑的另一个选择是保持整个IntMap和当前“下一个”ID作为状态,但这似乎非常“必要”和极端。 This问题非常相似,但没有得到很多答案(或者我可能只是错过了一些明显的答案)。在程序执行过程中利用状态monad获取唯一ID的惯用方法是什么?感谢。
答案 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 m
是Monad
,则 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 -}
如果t
为StateT 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)
请注意,我仍然使用get
和put
。那是因为transformers
包实现了我描述的所有概念,并将get
和put
的签名概括为:
get :: (Monad m) => StateT s m s
put :: (Monad m) => s -> StateT s m ()
这意味着它们会自动在StateT
内工作。 transformers
然后将State
定义为:
type State s = StateT s Identity
这意味着您可以对get
和put
使用State
和StateT
。
要了解有关monad变换器的更多信息,我强烈推荐Monad Transformers - Step by Step。