状態モナドの理解(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)])