あなたが望むのは、(パッケージから)と(パッケージから)の両方によって提供されるStateT s IO (String, Bool)
場所です。StateT
Control.Monad.State
mtl
Control.Monad.Trans.State
transformers
この一般的な現象はモナド変換子と呼ばれ、 Monad Transformers, Step by Stepで素晴らしい紹介を読むことができます。
それらを定義するには 2 つのアプローチがあります。それらの1つは、クラスをtransformers
使用してそれらを実装するパッケージにあります。MonadTrans
2 番目のアプローチはクラス内にmtl
あり、各モナドに個別の型クラスを使用します。
このアプローチの利点はtransformers
、単一の型クラスを使用してすべてを実装することです (ここにあります)。
class MonadTrans t where
lift :: Monad m => m a -> t m a
lift
MonadTrans
のすべてのインスタンスが満たさなければならない2 つの優れたプロパティがあります。
(lift .) return = return
(lift .) f >=> (lift .) g = (lift .) (f >=> g)
これらは変装した関手法則であり、(lift .) = fmap
wherereturn = 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)
これらは単なる変装したモナド則 wherethrowError = return
とcatchError = (>>=)
(そしてモナド則は変装した圏則 wherereturn = id
と(>=>) = (.)
) です。
特定の問題について、プログラムを作成する方法は同じです。
do
-- get the number of games from the command line (already written)
results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]
...しかし、playGame
関数を記述すると、次のようになります。
-- transformers approach :: (Num s) => StateT s IO ()
do x <- get
y <- lift $ someIOAction
put $ x + y
-- mtl approach :: (Num s, MonadState s m, MonadIO m) => m ()
do x <- get
y <- liftIO $ someIOAction
put $ x + y
複数のモナド変換子を積み上げ始めると、より明白になるアプローチ間の違いがもっとありますが、今のところは良いスタートだと思います。