Was Sie wollen, ist StateT s IO (String, Bool)
, wo StateT
sowohl Control.Monad.State
vorgesehen ist (aus dem mtl
-Paket) und Control.Monad.Trans.State
(aus dem transformers
Paket).
Dieses allgemeine Phänomen wird als Monade-Transformator bezeichnet, und Sie können eine gute Einführung zu ihnen in Monad Transformers, Step by Step lesen.
Es gibt zwei Ansätze, sie zu definieren. Eine davon befindet sich im Paket transformers
, das die Klasse MonadTrans
verwendet, um sie zu implementieren. Der zweite Ansatz befindet sich in der Klasse mtl
und verwendet eine separate Typklasse für jede Monade.
Der Vorteil des transformers
Ansatz ist die Verwendung eines einzigen Typ-Klasse ist alles zu implementieren (gefunden here):
class MonadTrans t where
lift :: Monad m => m a -> t m a
lift
hat zwei nette Eigenschaften, die jede Instanz von MonadTrans
erfüllen muss:
(lift .) return = return
(lift .) f >=> (lift .) g = (lift .) (f >=> g)
Dies sind die functor Gesetze in Verkleidung, wo (lift .) = fmap
, return = id
und (>=>) = (.)
.
Der mtl
Typ-Class-Ansatz hat seine Vorteile, auch, und einige Dinge nur sauber sein können gelöst mit den mtl
Typ-Klassen, aber der Nachteil ist dann, dass jede mtl
Typ-Klasse ihre eigenen Gesetze hat man sich erinnern, wenn Instanzen für es implementieren. Zum Beispiel kann die MonadError
Typ-Klasse (gefunden here) ist definiert als:
class Monad m => MonadError e m | m -> e where
throwError :: e -> m a
catchError :: m a -> (e -> m a) -> m a
Diese Klasse von Gesetzen kommt, auch:
m `catchError` throwError = m
(throwError e) `catchError` f = f e
(m `catchError` f) `catchError` g = m `catchError` (\e -> f e `catchError` g)
Dies sind nur die Monade Gesetze in der Verkleidung, wo throwError = return
und catchError = (>>=)
(und die Monad-Gesetze sind die Kategorie-Gesetze in Verkleidung, wo return = id
und (>=>) = (.)
).
Für Ihr spezielles Problem, wie Sie Ihr Programm schreiben würde wäre das gleiche:
do
-- get the number of games from the command line (already written)
results <- mapM (\game -> playGame game getStdGen) [1..numberOfGames]
... aber wenn Sie Ihre playGame
Funktion schreiben, würde es aussehen, entweder wie:
-- 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
Es gibt mehr Unterschiede zwischen den Ansätzen, die deutlicher werden, wenn Sie mit dem Stapeln von mehr als einem Monade-Transformator beginnen, aber ich denke, das ist ein guter Anfang.
Sie könnten auch 'RandT' aus dem [MonadRandom] (http://hackage.haskell.org/package/MonadRandom) Paket genießen. –