めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

State MonadでWriter Monadを実装する

似たものを比較することで、いままで気づかなかった新しい知見が得られることがあります。

などと、小難しい言い方をしなくても、1つ1つ勉強していてもよく分からなかったものが、2つ並べて勉強すると、よく分かったりすることありますよね。

Monadの応用例でよく出るState MonadとWriter Monadですが、よ〜く考えると似ています。この2つを比較すると、Writer MonadはState Monadの特殊な場合と理解することができてしまいます。

State MonadとWriter Monadを見比べる

まずは、比較しやすいように表記を工夫した俺々実装です。

newtype MyWriter a = MyWriter { runWriter :: ( a, [String] ) }

instance Monad MyWriter where
    return a    = MyWriter $ (a, [])
    m >>= k     = MyWriter $
                    let (a, log1) = runWriter m;
                        (b, log2) = runWriter (k a)
                    in (b,log1 ++ log2)
-- m :: MyWriter a
-- k :: a -> MyWriter a

newtype MyState s a = MyState { runState :: ( s -> (a, s) ) }

instance Monad (MyState s) where
    return a    = MyState $ \s -> (a,s)
    m >>= k     = MyState $ \s ->
                    let (a, s')  = runState m $ s;
                        (b, s'') = runState (k a) $ s'
                    in (b, s'')
-- m :: MyState s a
-- k :: a -> MyState s a

確かによく似ています。

State Monadはユーザが与えたState :: sの初期値をもとに、各Monadがその値を変換していきます。

一方、Writer Monadは、初期値がのログ情報 :: [String]に対して、各Monadが新しいログ :: Stringを追加していきます。

という事は、State MonadのState :: sとして、[String]型を指定して、初期値を与えれば、Writer Monadとして使える気がしてきます。

State MonadをWriter Monadとして使う

というわけで使ってみました。

addWlog :: String -> (Int -> MyWriter Int)
addWlog log = \x -> MyWriter $ (x, [log] )

addSlog :: String -> (Int -> MyState [String] Int)
addSlog log = \x -> MyState $ \s -> (x, s ++ [log])

main :: IO()
main = do
    let b = return 99 :: MyWriter Int
    print $ runWriter ( b >>= addWlog "hoge" >>= addWlog "hoga" )

    let c = return 99 :: MyState [String] Int
    print $ runState ( c >>= addSlog "hoge" >>= addSlog "hoga" ) []

実行結果はこちら。

$ ./state 
(99,["hoge","hoga"])
(99,["hoge","hoga"])

Writer Monadは、bind(>>=)によるログの演算が"log1 ++ log2"にハードコーディングされているので、addWlogでは追加するログだけを返せばよいのに対して、State Monadは、Stateの演算を自由に実装できるので、addSlogでは、前から受け継いだログに新しいログを追加する操作(s ++ [log])を明示的に記述しています。そして、runStateに初期値[]を与えています。

というわけで、Writer MonadはState Monadを特定の使い方に限定したものと見ることができました。すっきり。

次は、Reader MonadとState Monadも比較してみてください。