読者です 読者をやめる 読者になる 読者になる

みどりねこ日記

よくわからないけど、頑張りますよ。

Haskellモナドトランスフォーマー(12)

今まで、MaybeTとListTという2つの非常に単純なモナドトランスフォーマーの実装をしてきました。それから、少し遠回りをしてモナドをそれのトランスフォーマーに持ちあげるという話もしましたね。ここでは、その二つの考えを、StateTという標準ライブラリの中でも興味深いトランスフォーマーの実装を見ながらつなげてみましょう。このトランスフォーマーを学ぶことで、モナドトランスフォーマーをコードの中で使用するときに、トランスフォーマーのメカニズムがはっきりと見えてくるようになります。もしもわかりにくかったら、過去のStateモナドについての記事を読んでみてください。

Stateモナド

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

と定義されているように、StateTトランスフォーマーも

newtype StateT m a = StateT { runStateT :: (s -> m (a, s)) }

と定義されています。
State sはMonadクラスとMonadState sクラスのどちらのインスタンスでもあります。なので、StateT s mもMonadとMonadState sクラスのメンバーですし、更にいうと、もし、mがMonadPlusのインスタンスならStateT s mもまたMonadPlusのメンバーであるべきです。

StateT s mをMonadインスタンスとする実装は以下のようになります。

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

このreturnの定義は、内側のモナドのreturnを使用し、それを結果とし、>>=演算子は内側のモナドの計算をするためにdoブロックを使用しています。

また、StateTトランスフォーマーを使用し、MonadStateクラスのインスタンスとするすべての合成モナドも定義したいのでgetとputを定義しましょう。

instance (Monad m) => MonadState s (StateT s m) where
	get = StateT $ \s -> return (s, s)
	put s = StateT $ \_ -> return (( ), s)

MonadPluのインスタンスとするために、StateTがMonadPlusとともに使用される合成モナドの定義もしましょう。

instance (MonadPlus m) => MonadPlus (StateT s m) where
	mzero = StateT $ \s -> mzero
	(StateT x1) `mplus` (StateT x2) = StateT $ \s -> (x1 s) `mplus` (x2 s)

最後に、StateTモナドをMonadTransクラスのインスタンスとしてliftもつけ、Haskellのモナドクラスとして完成させましょう。

instance MonadTrans (StateT s) where
	lift c = StateT $ \s -> c >>= (\x -> return (x, s))

関数liftは、内側のモナドの計算を、入力された状態に結びつける関数に結びつける、状態変換関数StateTを作り出します。結果として、例えばStateTをListモナドに適応した時、結果としてリストを返す関数(Listモナド内の計算)は、StateT s [ ]に持ち上げられ、StateT (s -> [ (a, s) ]を返す関数になります。持ち上げられた計算は複数の (値, 状態) というペアを入力より生成し返します。
この結果として、StateTの計算をコピーして、持ち上げられた計算によって返されたリスト内のそれぞれの計算結果に対して違う計算を生成すます。もちろん、StateTを違うモナドに対して適応するのは違うセマンティクスのliftを生成します。