この記事はモナド(プログラミング)の一部として作成された内容を元にしています。
Stateモナドも値を中に内包しているが、ちょっと変形された形で保管している。
状態 state から新しい状態 newState = p(state) と値xを作る関数、という形で保管している。
unit()とflatMap()がどのような働きをするか検討すると多少は全貌が見えてくるのではないかと思う。
unitState()は後回しにして、どういう状態で保管しているかを述べる。
λ state → (x_1(state), p(state))
ただし、p(state)はstateを引数にし同じ型の違う値(正確には、同じとは限らない値)を返す関数。x_1(state)は、stateを引数とし、値xを作る関数。
つまりStateモナドは関数の組(x_1, p)であると言うことができる。以降この形で表現する。
λ state → (_, p(state)) が、箱モデルで出てきた外箱の部分に相当する(Haskellではこの関数がStateモナドそのものではなくレコード(データ)の一つになっているが、実質的には同じことである)。
Stateモナドは x 以外に、λ state → (_, newState) の外箱の部分にも情報を保持している。つまり情報が増えるモナドなのである。x に対応するモナドは unitState(x) 以外にも存在するということになる。
さてunitState() が未定義だったが、外箱の部分に関する情報が存在しない以上、
unitState(x) = λ state1 → (x, state1) というstateの部分に変更を加えない関数(実際は単位元則で規定される)になる。
unitState(x) = (constantx, id)
ただし、constantx(state) = x という定数関数、id(state) = state という恒等関数である。
箱モデルは滅びぬ。何度でもよみがえるさ。2段にすれば説明できるだろうか。
HTMLではtableでレイアウトするなというけれど、divでは不本意にずれるのでtableで図示すると、
unitState(x) = | constantx | = | λ state → x |
id | λ state → state |
次に、flatMapState() の第二引数となる関数について述べる。
λ x → (λ state → (y, q(state)))
ただし、q(state)はstateを引数にし同じ型の違う値を返す関数。
という高階関数である。つまりflatMapState()は(関数を戻り値とする)高階関数を引数にとる高階関数である。
Stateモナドs = (s1, s2)
f(x) = (f1(x), f2(x))
とする。上述のように、s1, s2 はそれぞれ関数であるが、f1(x), f2(x) だけでなく f1(x), f2(x) 自体も関数になっているということである。すなわちstateに「関数」 f1(x) を適用して f1(x)(state) とすることができるということである。
flatMapState() の動作は、一言で言えば「状態を引き継ぎながら次の計算を行う」ことであるが、実際の動作は一言では言い表し難いものとなっている。
s `flatMapState` f の中身を式1本で書くと理解不能になること請け合いなので、順を追って説明する。
s `flatMapState` f = (s1, s2) `flatMapState` (f1, f2)
= (λ state → f1(s1(state))(s2(state)), λ state → f2(s1(state))(s2(state)))
s | f | |||||
state | → | s1(state) | → | f1(s1(state)) | → | f1(s1(state))(s2(state)) |
↓ | ↘ | s2(state) | ↘ | f2(s1(state)) | → | f2(s1(state))(s2(state)) |
↓ | ↘ | → | → | ↑ | ↑ | |
↓ | ↑ | |||||
↓ | s `flatMapState` f | ↑ | ||||
↘ | → | → | → | → | → | ↗ |
もうやめて!とっくに編集者ののライフはゼロよ! たぶん読者もね
ここで s `flatMapState` (f `composeState` g) について考えてみる。
s `flatMapState` f `flatMapState` g
s | f | g | ||||||||
state | → | s1(state) | → | f1(s1(state)) | → | f1(s1(state))(s2(state)) | → | g1(f1(s1(state))(s2(state))) | → | g1(f1(s1(state))(s2(state)))(f2(s1(state))(s2(state))) |
↓ | ↘ | s2(state) | ↘ | f2(s1(state)) | → | f2(s1(state))(s2(state)) | ↘ | g2(f1(s1(state))(s2(state))) | → | g2(f1(s1(state))(s2(state)))(f2(s1(state))(s2(state))) |
↓ | ↘ | → | → | ↑ | ↘ | → | ↑ | ↑ | ||
↓ | ↑ | |||||||||
↓ | s `flatMapState` f `flatMapState` g | ↑ | ||||||||
↘ | → | → | → | → | → | → | → | → | ↗ |
値でなく矢印を見る。
どのように使おうとも個人の自由なので、使いどころを書いたとしてもそれは書いた人の意見に過ぎないので誤りでないとは言い切れない。誤りを書いてしまうことを恐れてか、解説書などをはじめとして多くの場合で、動作を述べただけで説明が終わっているか、「状態付き計算を純粋にすることが出来ました」という教科書的表現にとどまっていることが多い。この記事はどうせ間違いだらけなので、読書感想文くらいのつもりで使いどころを書く。
純粋関数型言語では、「状態」が結果(戻り値)に影響する場合は「状態」も引数に入ってくる。計算するたびに「状態」が更新される場合、純粋関数型言語では再代入が禁止されているので状態が更新されるたびに新しい変数名を割り当てる必要が生じ、名前空間を圧迫する。
Stateモナドを用いると、変数名を割り当てずともStateに含まれた値として次の計算の引数に渡されるので、変数名の割り当てが不要になる。多分Stateモナドの効用は、更新される状態にいちいち変数名を割り当てずに済むということだけであり、それ以外の場面ではStateモナドを使用しなくても大差なく書くことができるはずである。
非純粋関数型言語では、Stateモナド内に相当する計算の間だけ、再代入可能な局所変数を使用している状態に似ているといえる。イミュータブルを奨励する場合でも局所的に再代入可能な変数を使用することは、ミスの誘発が少ないとして許容することが多いような気がする。
掲示板
掲示板に書き込みがありません。
急上昇ワード改
最終更新:2024/04/24(水) 09:00
最終更新:2024/04/24(水) 09:00
ウォッチリストに追加しました!
すでにウォッチリストに
入っています。
追加に失敗しました。
ほめた!
ほめるを取消しました。
ほめるに失敗しました。
ほめるの取消しに失敗しました。