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

みどりねこ日記

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

Haskellのモナド2(5)

Stateモナドはコンテナという見方より、計算と見たほうがいいと思います。Stateモナドは「内部の値に依存したり、書き換えたりする計算」を表しています。例えば、3体問題(互いに相互作用する3体以上からなる系を扱う問題)のモデリングをしているとします。内部状態は、すべての体の位置と、質量と速度となります。もしある体の加速度を求める関数を作ろうとしたら、その関数はこの状態を計算の一部として参照する必要があります。

Stateモナドの計算の中でも、特に重要なのは内部の値を書き換えることができるということです。再び三体問題について考えますが、ある体に加速度を与える関数を考えると、その関数はその体の位置を書き換える必要がありますよね。

Stateモナドは、MaybeモナドやListモナドとは違って、「計算の結果を表す」のではなくて、「計算の中に含まれる特定の状態」を表すものです。

状態を引数として取って、それに依存する計算を関数としてモデリングしてみましょう。例えば、

f :: String -> Int -> Bool

という関数があったとして、これを型sに依存する関数に書き直したいとすると、fは

f :: String -> Int -> s -> Bool

となります。関数が状態sを書き換えられるようにするには、関数が(結果, 新しい状態)というペアを返すようにします。なので、fは

f :: String -> Int -> s -> (Bool, s)

となります。

この方法で書いていくと、多少面倒ですね。しかし、そんなに悪いものでもありません。もし2つの状態依存の計算(計算をf, gとすると、fの結果をgに渡す計算)をつなげようとすると、どうなるのでしょうか。2つめのgはfの結果と新しい状態を受け取る必要があります。ですので、「状態をつなげる」という形になります。

fThenG :: (s -> (a, s)) -> (a -> s -> (b, s)) -> s -> (b, s)
fThenG f g s =
	let (v, s') = f s  -- fを初期の状態で計算する
		(v', s'') = g v s'  -- gをfの結果(結果と状態)で計算する
	in (v', s'')  -- 最新の状態とgの結果を最終的な結果とする

この煩わしい操作はStateモナドで綺麗に覆い隠すことが出来ます。型コンストラクタStateは、初期状態と結果の型の2つのパラメータを取ります。結果の状態が結果のペアの最後にあるとしても、状態は引数として最初に取る必要があります。なぜなら、本当のモナドは特定の型の状態に束縛されますが、結果の状態の型はどれにでも成り得るからです。
さて、State s aは、初期状態sをとり、それに依存+書き換えを行え、型aの結果を返すものであることを示しています。どのように定義されているのでしょうか?シンプルに、状態をとって(計算結果, 状態) のペアを返す関数として定義します。

newtype State s a = State (s -> (a, s))

もう気づかれたかもしれませんが、上のfThenGは、Stateモナドの>>=の定義です。