状態モナドを作ってみる
typescriptで状態モナドを作ってみます。
状態モナドは
という状態を受け取って結果と変化した状態を返す関数です。
これを
export type State<R,S> = (s:S) => {result:R,state:S}
と定義しておきます。
初期値を作る関数を
export const unit = <R,S>(result:R) => (state:S) => { return {result:result, state:state}};
と定義します。これで
const m0 = unit<number,number>(100);
のように初期入力値を作ることができます。
次に数を2倍する関数を作りましょう。そして関数が呼ばれる回数を状態としてカウントすることを考えます。
具体的には
const f = (r:number) => (s:number) => { return {result:r*2, state:s+1}}
のような関数を用意します。つまり、状態を受け取ったら値を2倍しつつ状態を更新して返すという関数です。
これは
const f: (number) =>State<number, number>
という型を持っていることに注意しましょう。
作っておいた状態モナドm0にこの関数を適用するための関数としてbindを用意します。
export const bind = <R,S,Q>(m:State<R,S>, f:(r:R)=>State<Q,S>) => (s:S) => { const next = m(s); return f(next.result)(next.state); }
これは新しく状態モナドを作って、その状態モナドでは、元の状態モナドを先に使って結果を求め、それにfを適用するようにしてあります。
bindを使って、m0にfを3回くらい適用させてみましょう。
const m0 = unit<number,number>(100); const m1 = bind(m0,f); const m2 = bind(m1,f); const m3 = bind(m2,f);
m1,m2を用いたのはわかりやすさのためで、bindを連続して作用させても構いません。
できた結果の状態モナドm3を使うには初期状態を設定する必要があります。
const ans = m3(0)
ans.result == 800
ans.state == 3
となり、きちんとカウントしていることがわかると思います。
状態モナドの説明はこれで終わりです。
色々な使い方がありそうだということがわかると思います。
以上をまとめたコードがこちらになります。
export type State<R,S> = (s:S) => {result:R,state:S} export const unit = <R,S>(result:R) => (state:S) => { return {result:result, state:state}}; export const bind = <R,S,Q>(m:State<R,S>, f:(r:R)=>State<Q,S>) => (s:S) => { const next = m(s); return f(next.result)(next.state); } export function stateTest() { /// double number , state: count of operation const f = (r:number) => (s:number) => { return {result:r*2, state:s+1}} const m0 = unit<number,number>(100); const m1 = bind(m0,f); const m2 = bind(m1,f); const m3 = bind(m2,f); const display = <R>(m:State<R,number>) => { console.log("result:"+m(0).result + " state:"+m(0).state); } display(m0); display(m1); display(m2); display(m3); /* result:100 state:0 result:200 state:1 result:400 state:2 result:800 state:3 */ } stateTest();
ありがとうございました。