你需要的是
StateT s IO (String, Bool)
,其中
StateT
由
mtl
包和
transformers
包中的
Control.Monad.State
和
Control.Monad.Trans.State
提供。
这种普遍现象被称为单子变换器,你可以在
单子变换器逐步介绍 中阅读一个很好的介绍。
有两种定义它们的方法。其中一种方法在
transformers
包中找到,它使用
MonadTrans
类来实现它们。第二种方法在
mtl
类中找到,并为每个单子使用一个单独的类型类。
transformers
方法的优点是使用单个类型类来实现所有内容(在
此处 找到)。
class MonadTrans t where
lift :: Monad m => m a -> t m a
lift
具有两个很好的特性,任何 MonadTrans
的实例都必须满足这些特性:
(lift .) return = return
(lift .) f >=> (lift .) g = (lift .) (f >=> g)
这些是伪装成函数子定律的内容,其中
(lift .) = fmap
,
return = id
和
(>=>) = (.)
。
mtl
类型类方法也有其优点,有些问题只能使用
mtl
类型类干净地解决,但缺点是每个
mtl
类型类都有自己的一套规则,你必须记住它们,以便实现它们的实例。例如,
MonadError
类型类(在
这里找到)被定义为:
class Monad m => MonadError e m | m -> e where
throwError :: e -> m a
catchError :: m a -> (e -> m a) -> m a
这个类也有它的规则:
m `catchError` throwError = m
(throwError e) `catchError` f = f e
(m `catchError` f) `catchError` g = m `catchError` (\e -> f e `catchError` g)
这只是伪装成单子律的东西,其中throwError = return
和catchError = (>>=)
(而单子律则是类别法则的伪装,其中return = id
和(>=>) = (.)
)。
对于您的具体问题,您编写程序的方式将是相同的:
do
results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]
...但是当你编写playGame
函数时,它可能看起来像:
do x <- get
y <- lift $ someIOAction
put $ x + y
do x <- get
y <- liftIO $ someIOAction
put $ x + y
当您开始堆叠多个单子变换器时,这些方法之间的差异会变得更加明显,但我认为现在这是一个很好的开始。
MonadRandom
包中的RandT
。 - Daniel Wagner