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

StateT モナド変換子のしくみ

State モナドの作り方で分かるように、モナドの作り方は非常にシンプルだ。代数的データ型をつくり、その型を Monad タイプクラスのインスタンスにして。return 関数と >>= 演算子の実装をするだけだからだ。

非常に単純なデータ型の場合は次のようになるし、

Prelude> :set +m
Prelude> newtype M a = M a deriving Show
Prelude> instance Monad M where
Prelude|   return x = M x
Prelude|   (M x) >>= f = f x
Prelude|
Prelude> M 2 >>= \x -> M (x*2)
M 4

State モナドの場合は、以下のようになる。

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

return と >>= の定義が複雑に見えるが、本質は return a は引数 a をデータコンストラクタ m に包んで m a として返すだけだし、m a >>= f の場合は右項の m a からコンテナの中の a を取り出して、これに f を関数適用させた値 f a を返すが、これはデータコンストラクタ m に結果がくるまれた m b 型になる。単純モナドと State モナドの定義の違いはそのための調整を行っているだけだ。

また、>>= の右項の f はモナドの Kleisli 射であって、型は f :: a -> m b のように、引数一つをとり、モナド値 m b を返す関数だ。

これだけのポイントを押さえておけば、単純モナドも State モナドも同じ事をやっている事がわかる。

それはモナド変換子である StateT の場合もあまり事情はかわらない。StateT モナドの代数的データ型の定義と、Monad タイプクラスの多相関数である return と >>= の実装は次のようになる。

Prelude> newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) }
Prelude> instance (Monad m) => Monad (StateT s m) where
Prelude|   return a = StateT $ \s -> return (a,s)
Prelude|   (StateT x) >>= f = StateT $ \s -> do
Prelude|     (v,s') <- x s
Prelude|     runStateT (f v) s'
Prelude|
Prelude> runStateT (return 2) 3
(2,3)

StateT 型のデータがモナドになるには上の定義で十分だ。しかし、StateT モナドは MonadState クラスの get、put や MonadTrans クラスの lift などの多相関数がなければ真価が発揮できない。これらを多相関数として使うためには、StateT モナドを MonadState や MonadTrans などのタイプクラスのインスタンスにする必要があるが、動作確認のためとしては煩雑になるので、ここでは、普通の関数として定義する。

Prelude> let
Prelude| get :: StateT s IO s
Prelude| get = StateT $ \s -> return (s,s)
Prelude|

Prelude> let
Prelude| put :: s -> StateT s IO ()
Prelude| put s = StateT $ \_ -> return ((),s)
Prelude|

Prelude> let
Prelude| lift :: IO a -> StateT s IO a
Prelude| lift c = StateT $ \s -> c >>= (\x -> return (x,s))
Prelude|

get、put は基本的に State のものと同じだが、戻値が (s,s) ではなくて return (s,s) のように (s,s) をモナドでくるんで m (s,s) の形で返すようにしている。

lift c の場合はアクション c が実行されて、その結果の IO a のコンテナの値 a と状態 s の値のペア (a,s) を作り、return でモナドにくるんで m (a,s) として返している。

get、put、lift を使って StateT モナドの動作を確認してみた。

Prelude> runStateT (do x <- get; put (x*2); lift (print x); return x) 3
3
(3,6)

StateT モナドの動作検証がちょっと面倒だったが、このエントリーで言いたかったのは、ユーザ定義のデータ型をモナドにするのは、それらのデータ型を Monad クラスのインスタンスにして return と >>= の実装をするだけだということだ。

モナドの機能を拡張するのためには、他のタイプクラスをつくり多相関数を実装する。ただ、その際に関数の型指定に特殊な方法を使うらしくてそこのところがまだ理解できていない。自前のモナドをきちんと作れるようになるのはまだ先のようだ。

モナドで多相関数が多用されるのは、それが、統一されたインターフェースを提供するのに便利だからだ。return 関数にしても、>>= 演算子にしても多相関数にしなくても良いが、タイプクラスを活用して多相関数を記述することによって、様々なモナドに対して統一された操作性を提供することができる。

タイプクラスを単に多相関数を定義するための仕組みだと捉えることが出来れば、newtype や class、instance などのキーワードを使った複雑な定義についても理解しやくすなる。

また、Haskell をいじってわかったのが、かなりコンパイラよりの事まで調べることができるということだ。ブラックボックスが少ないというのはユーザとしてはありがたい。
by tnomura9 | 2013-05-04 01:29 | Haskell | Comments(0)
<< All about Monad... MyState モナドの作り方 >>