三つのモナドの能力を追加するRWSTモナド変換子

 RWSモナドのモナド変換子版であるRWSTモナド変換子も説明しておきましょう。RWSTモナド変換子の定義は以下の通りです。

newtype RWST r w s m a = RWST { runRWST :: r -> s -> m (a, s, w) }

evalRWST :: (Monad m) => RWST r w s m a -> r -> s -> m (a, w)
evalRWST m r s = do
    ~(a, _, w) <- runRWST m r s
    return (a, w)

execRWST :: (Monad m) => RWST r w s m a -> r -> s -> m (s, w)
execRWST m r s = do
    ~(_, s', w) <- runRWST m r s
    return (s', w)

mapRWST :: (m (a, s, w) -> n (b, s, w')) -> RWST r w s m a -> RWST r w' s n b
mapRWST f m = RWST $ \r s -> f (runRWST m r s)

withRWST :: (r' -> s -> (r, s)) -> RWST r w s m a -> RWST r' w s m a
withRWST f m = RWST $ \r s -> uncurry (runRWST m) (f r s)

instance (Monad m) => Functor (RWST r w s m) where
    fmap f m = RWST $ \r s -> do
        ~(a, s', w) <- runRWST m r s
        return (f a, s', w)

instance (Monoid w, Monad m) => Monad (RWST r w s m) where
    return a = RWST $ \_ s -> return (a, s, mempty)
    m >>= k  = RWST $ \r s -> do
        ~(a, s', w)  <- runRWST m r s
        ~(b, s'',w') <- runRWST (k a) r s'
        return (b, s'', w `mappend` w')
    fail msg = RWST $ \_ _ -> fail msg

instance (Monoid w, MonadPlus m) => MonadPlus (RWST r w s m) where
    mzero       = RWST $ \_ _ -> mzero
    m `mplus` n = RWST $ \r s -> runRWST m r s `mplus` runRWST n r s

~ 略 ~

instance (Monoid w, Monad m) => MonadReader r (RWST r w s m) where
    ask       = RWST $ \r s -> return (r, s, mempty)
    local f m = RWST $ \r s -> runRWST m (f r) s

instance (Monoid w, Monad m) => MonadWriter w (RWST r w s m) where
    tell   w = RWST $ \_ s -> return ((),s,w)
    listen m = RWST $ \r s -> do
        ~(a, s', w) <- runRWST m r s
        return ((a, w), s', w)
    pass   m = RWST $ \r s -> do
        ~((a, f), s', w) <- runRWST m r s
        return (a, s', f w)

instance (Monoid w, Monad m) => MonadState s (RWST r w s m) where
    get   = RWST $ \_ s -> return (s, s, mempty)
    put s = RWST $ \_ _ -> return ((), s, mempty)

instance (Monoid w, Monad m) => MonadRWS r w s (RWST r w s m)

-- ---------------------------------------------------------------------------
-- Instances for other mtl transformers

instance (Monoid w) => MonadTrans (RWST r w s) where
    lift m = RWST $ \_ s -> do
        a <- m
        return (a, s, mempty)

instance (Monoid w, MonadIO m) => MonadIO (RWST r w s m) where
    liftIO = lift . liftIO

 RWSモナドをそのままモナド変換子にしただけです。RWSモナドや前回説明したReaderTモナド変換子とWriterTモナド変換子,第15回で説明したStateTモナド変換子の定義から類推できる定義なので,特に難しくはないでしょう。

 実際に使ってみましょう。第15回で説明したように,GHCiのプロンプト上ではモナドmをIOモナドだと判断して実行するので,runRWSをrunRWST,それ以外の*RWS関数も*RWSTにそれぞれ変更すれば,RWST IOモナドの実行結果を得られます。

Prelude Control.Monad.RWS> runRWST (ask >>= tell) "group" 0
((),0,"group")
Prelude Control.Monad.RWS> runRWST (local ("semi-" ++) ask >>= tell) "group" 0
((),0,"semi-group")
Prelude Control.Monad.RWS> runRWST (withRWST (\_ s -> ("category", s)) ask >>= tell) "group" 0
((),0,"category")
Prelude Control.Monad.RWS> runRWST (listen $ ask >>= tell) "group" 0
(((),"group"),0,"group")
Prelude Control.Monad.RWS> runRWST (listens ("semi-" ++) $ ask >>= tell) "group" 0
(((),"semi-group"),0,"group")
Prelude Control.Monad.RWS> runRWST (censor ("semi-" ++) $ ask >>= tell) "group" 0
((),0,"semi-group")
Prelude Control.Monad.RWS> runRWST (get >>= tell) "group" (Any False)
((),Any {getAny = False},Any {getAny = False})
Prelude Control.Monad.RWS> runRWST (get >>= tell >> tell (Any True)) "group" (Any False)
((),Any {getAny = False},Any {getAny = True})
Prelude Control.Monad.RWS> runRWST (get >>= tell >> put (Any True)) "group" (Any False)
((),Any {getAny = True},Any {getAny = False})
Prelude Control.Monad.RWS> runRWST (get >>= tell >> (liftIO . print) 0) "group" "category"
0
((),"category","category")
Prelude Control.Monad.RWS> evalRWST (get >>= tell >> (liftIO . print) 0) "group" "category"
0
((),"category")
Prelude Control.Monad.RWS> execRWST (get >>= tell >> (liftIO . print) 0) "group" "category"
0
("category","category")

 RWSTモナド変換子がきちんと機能しているのを確認できました。

もう一つのReaderモナド

 前回説明したように,mtlパッケージのControl.Monad.Readerモジュールでは,Readerモナドを定義するのにReader型構成子を使っていました。しかし,Readerモナドを定義するのにこのような新しい型を必ず使わなければならないわけではありません。

 実は,Haskellでは関数の型で使用する矢印「(->)」を「* -> * -> *」という種の型構成子として扱います(参考リンク)。GHCでは少し異なる形で定義されていますが,これはGHCでは非ボックス化型を#という別の種として定義しているためです。GHCでは,#と*の両方を扱えます(参考リンク1参考リンク2)。

Prelude> :k (->)
(->) :: ?? -> ? -> *

Prelude GHC.Exts> :set -XMagicHash
Prelude GHC.Exts> :k Int#
Int# :: #
Prelude GHC.Exts> :k (->) Int#
(->) Int# :: ? -> *
Prelude GHC.Exts> :k (->) Int# Int#
(->) Int# Int# :: **
Prelude GHC.Exts> :k (->) Int Int#
(->) Int Int# :: *
Prelude GHC.Exts> :k (->) Int# Int
(->) Int# Int :: *

 非ボックス化型を使わなければ,「(->)」は「* -> * -> *」と同じ意味だと考えて差し支えありません。

 このように「(->)」は型構成子として扱われるため,「(r ->)」を意味する「(->) r」という型に対してインスタンスを定義できるのです。実際に,baseパッケージのControl.Monad.Instancesモジュールでは,FunctorクラスとMonadクラスのインスタンスが「(->)」で定義されています。同様に,Control.Monad.ReaderモジュールではMonadReaderクラスのインスタンスが「(->)」で定義されています。

instance Functor ((->) r) where
        fmap = (.)

instance Monad ((->) r) where
        return = const
        f >>= k = \ r -> k (f r) r

instance MonadReader r ((->) r) where
    ask       = id
    local f m = m . f

 MonadReaderクラスのインスタンスを使えば,Readerモナドの感覚でプログラミングできます。

Prelude Control.Monad.Reader> ask "group"
"group"
Prelude Control.Monad.Reader> (return () >> ask) "group"
"group"
Prelude Control.Monad.Reader> (local ("semi-" ++) ask) "group"
"semi-group"
Prelude Control.Monad.Reader> (local ("semi-" ++) (return ()) >> ask) "group"
"group"

 前回説明したReader型を使った例と,意味は同じです。Reader型には格納しないことで不要になったrunReaderを省いているだけです。

Prelude Control.Monad.Reader> runReader ask "group"
"group"
Prelude Control.Monad.Reader> runReader (return () >> ask) "group"
"group"
Prelude Control.Monad.Reader> runReader (local ("semi-" ++) ask) "group"
"semi-group"
Prelude Control.Monad.Reader> runReader (local ("semi-" ++) (return ()) >> ask) "group"
"group"

 特別な型を用意しなくても,関数そのものをReaderモナドとして利用できるのです。


著者紹介 shelarcy

 2009年3月18日に発売されたSoftware Design 2009年4月号に,山下伸夫さんや同僚の伊藤勝利さんが「-Haskellで学ぶ- 関数プログラミングのことはじめ」という関数プログラミングの入門記事を書いています。山下さんは@ITで「のんびりHaskell」という初心者向けのHaskellの連載記事も執筆しています。たまには気分転換にこうした初心者向けの記事を読んでみてもよいかもしれませんね。