Prev: IO モナド TOC: 目次 Next: Reader モナド

State モナド


概観

計算のタイプ: 状態を保持する計算
束縛の戦略: 束縛は状態パラメータを束縛された関数を通じて一本に繋いぎ、同一の 状態値が2度使われることのないようにすることで、その場での更新を 行っているように見えます。
利用場面: 状態を共有する必要のある一連の操作から計算を構築する
ゼロおよびプラス: なし
型の例: State st a

動機

純粋な関数型言語では値をその場で更新することはできません。それが 参照透明性を破壊するからです。このような状態をもつ(stateful)計算 をシミュレートするのによく使うイディオムは、一連の関数を通して、 状態パラメータを「一本に繋ぐ」ことです。

data MyType = MT Int Bool Char Int deriving Show

makeRandomValue :: StdGen -> (MyType, StdGen)
makeRandomValue g = let (n,g1) = randomR (1,100) g
                        (b,g2) = random g1
                        (c,g3) = randomR ('a','z') g2 
                        (m,g4) = randomR (-n,n) g3
                    in (MT n b c m, g4)

このアプローチは上手くいきますが、このようなコードは乱雑で、 保守しにくく、間違いの温床になります。State モナドは状態パラメータを 一本に繋ぐ部分を束縛操作の内側に隠すと同時に、コードを書きやすく、 読みやすく、変更しやすいものにします。

定義

ここで示した定義では Haskell 98 の標準にはない、複数パラメータ型クラスお よび funDeps が使われています。この State モナドを利用するのに、これの 詳細を完全に理解する必要はありません。

newtype State s a = State { runState :: (s -> (a,s)) } 
 
instance Monad (State s) where 
    return a        = State $ \s -> (a,s)
    (State x) >>= f = State $ \s -> let (v,s') = x s in runState (f v) s' 

State モナド内の値は、ある初期値から、(value,newState) のペアへの 遷移関数として表現されます。また新しい型の定義は次のような構成に なっています。すなわち、State s a は、型 s の状態をもつ、State モナド内で型 a となる値の型です。

型構築子 State sMonad クラスのインスタンス です。return 関数は単純に状態遷移関数を生成します。 この関数は値をセットしますが、状態は変更しません。束縛演算子は、 右側の引数を左側に引数から来た値と新しい状態に適用します。

class MonadState m s | m -> s where 
    get :: m s
    put :: s -> m ()

instance MonadState (State s) s where 
    get   = State $ \s -> (s,s) 
    put s = State $ \_ -> ((),s) 

MonadState クラスは標準で提供されていますが、たいへん 単純な State モナドへのインタフェースを提供します。get 関数はそれをその値として複写することで状態を取り出す関数です。 put 関数はモナドの状態をセットするだけで、値を作らない 関数です。

ほかにも、get および put を使って複雑な計算を 実行する関数が多数提供されています。最も便利なもののひとつに、 状態の関数を取り出す gets という関数があります。そのほかは State モナドライブラリの 説明書にあります。

State モナドの単純なアプリケーションは、乱数生成器の 状態を生成器への複数の呼び出しを通じて一本に繋ぐことです。

example15.hs で使えるコード
data MyType = MT Int Bool Char Int deriving Show

{- Stateモナドを使うと、乱数を返し、同時に乱数発生器の状態を
   更新する関数を定義することができます。
-}
getAny :: (Random a) => State StdGen a
getAny = do g      <- get
            (x,g') <- return $ random g
            put g'
            return x

-- getAny と同じですが、こちらは返る乱数の範囲を制限します
getOne :: (Random a) => (a,a) -> State StdGen a
getOne bounds = do g      <- get
                   (x,g') <- return $ randomR bounds g
                   put g'
                   return x

{- StdGen を状態として State モナドを使うと、コードを通じて、
   乱数発生器の状態を一本に繋ぐのを手でおこなうことなく、乱雑な
   複合型を構築できます
-}   
makeRandomValueST :: StdGen -> (MyType, StdGen)
makeRandomValueST = runState (do n <- getOne (1,100)
                                 b <- getAny
                                 c <- getOne ('a','z')
                                 m <- getOne (-n,n)
                                 return (MT n b c m))

Prev: IO モナド TOC: 目次 Next: Reader モナド