状態モナドの理解(2)
(1) で示したパターンは、Haskell で既に状態モナドとしてパターン化されている。GHC 7.0.4 では状態モナドは以下のように定義されていた。
GHCi, version 7.0.4: http://www.haskell.org/ghc/ :? for help (略) Prelude> :module Control.Monad.State Prelude Control.Monad.State> :i State type State s = StateT s Data.Functor.Identity.Identity -- Defined in Control.Monad.Trans.State.Lazy Prelude Control.Monad.State> :i StateT newtype StateT s m a = StateT {runStateT :: s -> m (a, s)} -- Defined in Control.Monad.Trans.State.Lazy instance Monad m => Monad (StateT s m) -- Defined in Control.Monad.Trans.State.Lazy (略) Prelude Control.Monad.State> :t StateT StateT :: (s -> m (a, s)) -> StateT s m a Prelude Control.Monad.State> :t state state :: (s -> (a, s)) -> State s a Prelude Control.Monad.State> :i runState runState :: State s a -> s -> (a, s) -- Defined in Control.Monad.Trans.State.Lazy
StateT は、 s -> (a, s) の型の計算を包むデータ構造になっているようだ。runState はその計算を取り出す単なるフィールドラベルだ。これだけではよく分からないので、天下り的だが、実際に先の計算を書き換えてみる。書き換える際、Env -> (Value, Env) の型を、State Env Value に対応させる。ただし、State というデータ構築子は存在しないので、上記の state 関数を用いる。
import Control.Monad.State type Name = String type Value = Int type Env = [(Name, Value)] delVar :: Name -> State Env () delVar k = state $ \e -> delVar' k e delVar' k [] = ((), []) delVar' k ((k1,v1):ks) = if k1 == k then ((), ks) else ((), (k1, v1) : e') where (_, e') = delVar' k ks setVar :: Name -> Value -> State Env Value setVar k v = state $ \e -> case lookup k e of Just x -> (v, (k, v) : e') where (_, e') = delVar' k e Nothing -> (v, (k, v) : e) getVar :: Name -> State Env Value getVar k = state $ \e -> case lookup k e of Just x -> (x, e) Nothing -> error ("Invalid Key: " ++ k) incf :: Name -> State Env Value --incf k = getVar k >>= (\v -> setVar k (v + 1)) -- do notation ver. incf k = do v <- getVar k setVar k (v + 1) incf2 :: Name -> State Env Value incf2 k = incf k >> incf k incfN :: Name -> Value -> State Env Value incfN k n = do v <- getVar k setVar k (v + n)
関数だった delVar, setVar, 等が State Env Value 型になっているが、これは単に計算をラップしただけにすぎないので、実際上は関数のままである。このことは、一部の引数を与えてやるとよくわかる。
*Main> :t setVar setVar :: Name -> Value -> State Env Value *Main> :t setVar "x" setVar "x" :: Value -> State Env Value *Main> :t setVar "x" 10 setVar "x" 10 :: State Env Value *Main> :t runState (setVar "x" 10) runState (setVar "x" 10) :: Env -> (Value, Env) *Main>
例えば setVar は、新しいState モナド版の定義では、runState に引数として与えると、依然として Env -> (Value, Env) 型の関数であることが分かる。従って、 runState xxx にさらに Env 型の引数を与えると、(Value, Env) が返る。タプルでなく Value のみを返す evalState という関数も用意されている。
関数のつなぎあわせ
State モナド版では、前回定義した関数をつなぎ合わせる comb, comb_ 関数は不要となり、既に定義されている >>, >>= を用いることができる(もともと、comb, comb_ はモナドの関数を模倣して作った)。モナドという既に準備されたパターンを使うと、モナド用に作られた do 記法を使える。状態モナドと do 記法を使うと以下のように簡潔に書ける。
ex1 = do setVar "x" 10 setVar "y" 3 incf "x" x <- getVar "x" y <- getVar "y" return (x + y) -- runState ex1 [] -- => (14,[("x",11),("y",3)]) ex2 :: State Env Value ex2 = do incf "x" incf "x" incf "x" x <- getVar "x" return x -- runState ex2 [("x", 0)] -- => (3,[("x",3)])