Haskellのモナドいろいろ (3)

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