人気ブログランキング | 話題のタグを見る

MyState モナドの作り方

次のように、簡単なユーザ定義型のデータをモナドにするのはひどく簡単だった。

Prelude> :set +m
Prelude> newtype M a = M a deriving Show

Prelude> instance Monad M where
Prelude|   (M x) >>= f = f x
Prelude|   return x = M x
Prelude|

Prelude> M 2 >>= \x -> M (x * 2)
M 4

そこで、今度は State モナドをターゲットにして自前のモナドの作り方を調べようとしたが、当てが外れた。

Prelude> :m Control.Monad.State
Prelude Control.Monad.State> :t State

:1:1:
    Not in scope: data constructor `State'
    Perhaps you meant `StateT' (imported from Control.Monad.State)
Prelude Control.Monad.State> :info State
type State s = StateT s Data.Functor.Identity.Identity
        -- Defined in `Control.Monad.Trans.State.Lazy'

どうも State モナドは StateT モナドに統合されてしまっているようだ。State モナドの性質を知らずに StateT モナドを調べるのはつらいものがあるので、いっそのこと MyState モナドを作ってみようと思いついた。

古い State 型の定義は次のようになっている。

newtype State s a = State { runState :: (s -> (a,s)) }

State 型のパラメータは (s -> (a,s)) 型の関数で runState という名つきフィールドになっている。したがってこの関数はアクセサ runState で簡単に取り出すことができる。まず、これを真似して MyState 型を定義してみた。

Prelude> newtype MyState s a = MyState { runMyState :: s -> (a,s) }

次のように MyState 型のデータ st1 を定義してみると、確かに runMyState でコンテナの関数が取り出せるのが分かる。

Prelude> let st1 = MyState (\x -> ("hello", x*x))
Prelude|

Prelude> runMyState st1 2
("hello",4)

つぎに、この MyState 型を『 all about monads 』の記事を真似して Monad クラスのインスタンスにしてみる。

Prelude> instance Monad (MyState s) where
Prelude|   return a = MyState $ \s -> (a,s)
Prelude|   (MyState x) >>= f = MyState $ \s -> let (v,s') = x s in runMyState (f v) s'
Prelude|

これでめでたく MyState 型がモナドになったので return 関数を試してみる。

Prelude> runMyState (return 2 >>= \a -> MyState (\s -> (a,s))) 3
(2,3)

runMyState 関数の戻値のペアの fst 要素に return の値 2 が、snd 要素に状態の値の 3 が収められている。return 関数はうまくいったようだ。次に >>= (bind 演算子) を試すために次のような2つの MyState モナドの Kleisli 射を定義する。Kleisli 射とは引数が一つで戻値が MyState s a 型の関数だ。

Prelude> let
Prelude| kleisli1 a = MyState (\s -> (a, s*s))
Prelude| kleisli2 a = MyState (\s -> (a*a, s))
Prelude|

return 関数とこの2つの Kleisli 射を >>= で合成してためしてみると次のように上手く働くことが分かる。

Prelude> runMyState (return 2 >>= kleisli1) 3
(2,9)
Prelude> runMyState (return 2 >>= kleisli2) 3
(4,3)
Prelude> runMyState (return 2 >>= kleisli1 >>= kleisli2) 3
(4,9)

もちろん、do 記法も使える。

Prelude> runMyState (do x <- return 2; kleisli1 x) 3
(2,9)

このように自前の MyState 型をモナドにするのは意外に簡単なことが分かる。

もっとも上の MyState モナドには get 関数と put 関数がない。この2つの関数は MonadState タイプクラスの多相関数だが、ここでは一般の関数として定義してみる。

Prelude> let
Prelude| get = MyState $ \s -> (s,s)
Prelude| put s = MyState $ \_ -> ((),s)
Prelude|

それでは、MyState モナドの tick 関数を作ってみよう。

Prelude> let
Prelude| tick = do
Prelude|   x <- get
Prelude|   put (x+1)
Prelude|   return x
Prelude|

テストしてみる。

Prelude> runMyState tick 0
(0,1)
Prelude> runMyState (tick >> tick >> tick) 0
(2,3)

どうやら MyState モナドでも tick 関数はうまくはたらくようだ。

自前の状態モナドを作ってみたが、プロトタイプは意外に簡単にできることがわかった。自前のモナドを作るときの取っ掛かりができたような気がする。
by tnomura9 | 2013-05-03 12:38 | Haskell | Comments(0)
<< StateT モナド変換子のしくみ StateT モナドの使い方 >>