让我们想象一下我们要IO
- 使State
单子。那会是什么样子?我们的纯粹State
monad 只是一个新类型:
s -> (a, s)
嗯,IO
在返回最终值之前,版本可能会产生一些副作用,如下所示:
s -> IO (a, s)
这种模式非常常见,它有一个名字,特别是StateT
:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
这个名字有一个T
最后因为它是一个 monadT
变压器。我们称之为m
“基本单子”和StateT s m
“转变”的单子。
StateT s m
只是一个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 -}
If t
is StateT s
, then lift
的类型专门用于:
lift :: m a -> StateT s m a
换句话说,它让我们“提升”基础 monad 中的动作,使其成为转换后 monad 中的动作。
因此,对于您的具体问题,您想要StateT (IntMap k v) IO
单子,它延伸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
and put
to be:
get :: (Monad m) => StateT s m s
put :: (Monad m) => s -> StateT s m ()
这意味着它们会自动工作StateT
. transformers
然后就定义了State
as:
type State s = StateT s Identity
这意味着您可以使用get
and put
对彼此而言State
and StateT
.
要了解有关 monad 变压器的更多信息,我强烈推荐Monad 变形金刚 - 一步一步 http://www.cs.virginia.edu/~wh5a/personal/Transformers.pdf.