我想使用节点和唯一键的 IntMap 创建图形结构。这个话题在这里和这里已经很好地涵盖了. 我通过基本上将 state -> (val,state) 的函数包装在一个新类型中来理解 state monad 的工作原理,这样我们就可以为它创建一个 monad 实例。我已经阅读了很多关于这个主题的内容。我似乎仍然无法理解如何在整个程序执行过程中获得唯一(或只是增量)值。获得一系列连续的 ID 很容易,但是一旦我“runState”退出 monad,我似乎又回到了开始跟踪当前 ID 的地方。我觉得我被困在单子里了。我考虑的另一个选择是将整个 IntMap 和当前的“下一个”ID 保留为状态,但这似乎非常“必要”和极端。这问题非常相似,但没有得到很多答案(或者我只是遗漏了一些明显的东西)。在整个程序执行过程中,利用 state monad 获取唯一 ID 的惯用方法是什么?谢谢。
1 回答
假设我们要IO
-ify 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
的末尾有一个,因为它是一个单子变形器T
。我们称其m
为“基本单子”和StateT s m
“转换后的”单子。
StateT s m
is only a Monad
if m
is a 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
is StateT s
,则lift
's 类型专门用于:
lift :: m a -> StateT s m a
换句话说,它让我们“提升”基本单子中的一个动作,使其成为转换后的单子中的一个动作。
因此,对于您的特定问题,您需要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
and 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
and和。put
State
StateT
要了解有关 monad 转换器的更多信息,我强烈推荐Monad Transformers - Step by Step。