読者です 読者をやめる 読者になる 読者になる

はわわーっ

はわわわわっ

Stateモナドでモナド変換子を使ってみる

Stateモナドを使ってモナド変換子のお勉強。
前回みたいにスタックの pop と push を作る。
で、そのときにメッセージを表示するようにしてみる。

import Control.Monad.State

pop :: StateT [Int] IO Int
pop = do
  (x:xs) <- get
  put xs
  liftIO $ putStrLn $ "pop " ++ show x
  return x

push :: Int -> StateT [Int] IO ()
push x = do
  xs <- get
  put (x:xs)
  liftIO $ putStrLn $ "push " ++ show x
  return ()

型が State から StateT になっている。

StateT :: (s -> m (a, s)) -> StateT s m a

s が状態、m が組み合わせるモナド、a が状態付き計算の結果。m が増えた以外は Stateモナドと同じ。
pop も push も return する前にメッセージを表示するようにしてある。
liftIO は

liftIO :: IO a -> m a

で、IOモナドを別のモナド(今は StateT [Int] IO)に変換する。
とりあえず、これらを使ってみる。

exampleStateT :: StateT [Int] IO ()
exampleStateT = do
  push 1
  push 2
  pop
  push 3
  pop
  push 4
>>> (a, s) <- runStateT exampleStateT []
push 1
push 2
pop 2
push 3
pop 3
push 4
>>> a
()
>>> s
[4,1]

runStateT exampleStateT [] の型が

runStateT exampleStateT [] :: IO ((), [Int])

になるので、それを (a, s) に束縛している。

同じような感じで、IOモナドの代わりに Writerモナドを使ってみる。
これも使い方はだいたい一緒。
今度は、pop や push を使う度にその時の状態を記録していく。

import Control.Monad.State
import Control.Monad.Writer

pop :: StateT [Int] (Writer [[Int]]) Int
pop = do
  (x:xs) <- get
  put xs
  tell [xs]
  return x

push :: Int -> StateT [Int] (Writer [[Int]]) ()
push x = do
  xs <- get
  put (x:xs)
  tell [x:xs]
  return ()

さっきの IO のところに (Writer \[\[Int]]) が入っている。
これを動かしてみる。

exampleStateT :: StateT [Int] (Writer [[Int]]) ()
exampleStateT = do
  push 1
  push 2
  pop
  push 3
  pop
  push 4
>>> let ((a, s), w) = runWriter $ runStateT exampleStateT []
>>> a
()
>>> s
[4,1]
>>> w
[[1],[2,1],[1],[3,1],[1],[4,1]]

今度は runStateT exampleStateT [] の型が

runStateT exampleStateT [] :: Writer [[Int]] ((), [Int])

になるので、これに runWriter を使うと

runWriter $ runStateT exampleStateT [] :: (((), [Int]), [[Int]])

になってそれぞれの値を取り出せる。

最後に、これに IOモナドを組み合わせてみる。

import Control.Monad.State
import Control.Monad.Writer

pop :: StateT [Int] (WriterT [[Int]] IO) Int
pop = do
  (x:xs) <- get
  put xs
  liftIO $ putStrLn $ "pop " ++ show x
  tell [xs]
  return x

push :: Int -> StateT [Int] (WriterT [[Int]] IO) ()
push x = do
  xs <- get
  put (x:xs)
  liftIO $ putStrLn $ "push " ++ show x
  tell [x:xs]
  return ()

さっきのやつを単に組み合わせただけ。Writer が WriterT になった以外は一緒。
動かしてみる。

exampleStateT :: StateT [Int] (WriterT [[Int]] IO) ()
exampleStateT = do
  push 1
  push 2
  pop
  push 3
  pop
  push 4
>>> ((a, s), w) <- runWriterT $ runStateT exampleStateT []
push 1
push 2
pop 2
push 3
pop 3
push 4
>>> a
()
>>> s
[4,1]
>>> w
[[1],[2,1],[1],[3,1],[1],[4,1]]

今度は

runWriterT $ runStateT exampleStateT []
  :: IO (((), [Int]), [[Int]])

となって、IOモナドの中に入っているので let なしで束縛できるようになった。

モナド変換子がなんとなく分かってきた気がする。