Stateモナド
STモナドは runST で値のみを取り出せたが、Stateモナドは状態も取り出せる。値と状態を取り出すには runState を、値のみを取り出すには evalState を、状態のみを取り出すには execState を用いる。
前の記事でSTモナドで実装した sum' 関数を Stateモナドを使って書き直すと下記のようになる。
Stateモナドの持つ状態を、破壊的代入可能な変数のようなものとして使っている。
import Control.Monad import Control.Monad.State sum' :: [Int] -> Int -- 戻り値が裸 sum' xs = (`execState` 0) $ do -- execStateで初期状態 0 を与え、結果の状態を取り出す forM_ xs $ \i -> modify (+ i) -- 状態を更新 main :: IO () main = do print $ sum' [1..10] -- 55
簡潔だがいささか分かりにくい。少し書き換えると下記のようになる。
Stateモナドの状態は get で取得し、put で更新するが、関数で更新する場合は modify を用いる。
import Control.Monad import Control.Monad.State sum' :: [Int] -> State Int () -- Sateでくるんだ状態Intと値()を返す sum' xs = do forM_ xs $ \i -> modify (+ i) -- 状態を更新 main :: IO () main = do let a = execState (sum' [1..10]) 0 -- execStateで初期状態 0 を与え、結果の状態を取り出す print a -- 55
モナドだから return で値を中に入れることができる。
import Control.Monad.State hoge :: Int -> State String Int -- 状態: String, 値: Int hoge x = return x main :: IO () main = do let ma = hoge 5 let a = runState ma "hoge" print a -- (5,"hoge")
モナドだから join で一皮むくことができるが、 join で裸にはできない。
import Control.Monad import Control.Monad.State hoge :: Int -> State s Int hoge x = return x main :: IO () main = do let ma = hoge 5 :: State String Int let mma = return ma :: State String (State String Int) let ma' = join mma -- State String Int -- let a' = join ma' -- エラー let a' = runState ma' "hoge" print a' -- (5,"hoge")
モナドだから fmap で中の値を関数に渡すことができる。
import Control.Monad.State hoge :: Int -> State String Int hoge x = return x main :: IO () main = do let ma = hoge 5 :: State String Int let mb = fmap (*2) ma let b = runState mb "hoge" print b -- (10,"hoge")
モナドだから bind ( >>= ) で中の値を、モナドを返す関数に渡すことができる。
import Control.Monad.State hoge :: Int -> State String Int hoge x = return (x * 2) main :: IO () main = do let ma = hoge 5 let mb = ma >>= hoge let a = runState ma "hoge" let b = runState mb "piyo" print a -- (10,"hoge") print b -- (20,"piyo")
またすでに見た通り、モナドだから do構文が使える。
do構文を使って状態付き計算をつなげることができる。
モナドを使わずに状態をいちいち引数として関数に渡すと、下記のように繁雑なコードになる。
fb :: Int -> Int -> Int fb x0 x1 = x0 + x1 main :: IO () main = do let x2 = fb 0 1 let x3 = fb 1 x2 let x4 = fb x2 x3 let x5 = fb x3 x4 let x6 = fb x4 x5 print x6 -- 8
Stateモナドを使うと下記のように書ける。(字下げに注意すること)
このようにStateモナドの状態はグローバル変数のようなものとして利用できる。
import Control.Monad.State fb :: State (Int, Int) Int fb = do (x0, x1) <- get let x2 = x0 + x1 put (x1, x2) return x2 main :: IO () main = do let x6 = (`evalState` (0, 1)) $ do x2 <- fb x3 <- fb x4 <- fb x5 <- fb fb print x6 -- 8