「関数型プログラミングの基礎」モナドを作るを理解する
と同じ本の、p.268~p.274あたりでつまずいたので、メモを残しておく。
コードはここにおいてある。
akimichi.github.io
モナドの機能は、「値にコンテキストを付加すること」、「コンテキストを付加したまま処理を合成すること」である。
unit
関数は、モナドのインスタンスを生成するための関数で、unit:: T => M[T]
という型情報を持つ。
T
はモナドに渡される値の型であり、M
はモナドの型である。
flatMap
関数は、処理を合成するための関数で、flatMap:: M[T] => FUN[T => M[S]] => M[S]
という型情報を持つ。
M[T]
というモナドのインスタンスを受け取ると、そのモナドから値を取り出してT => M[S]
という関数を適用し、その結果としてM[S]
を返す。
今回メモするモナドは、恒等モナドである。
恒等モナドは、値にコンテキストを付加することなく、そのまま値として扱う。
恒等モナドの関数を、以下のようにIDという名前空間に定義する。
const ID = { unit: (value) => { return value; }, flatMap: (instanceM) => { return (transform) => { return transform(instanceM); }; }, };
また、次のような関数を定義する。
const succ = (n) => { return n + 1; }; const double = (m) => { return m * 2; };
unit
関数は、単に値を返しているだけである。
ID.unit(1) == 1;
flatMap
関数を介して1
にsucc
関数を適用する処理は、succ(1)
と同じ結果になる。
ID.flatMap(ID.unit(1))((one) => { return ID.unit(succ(one)); }) == succ(1)
これを順を追ってみていく。
まず、ID.flatMap
は、引数ID.unit(1) = 1
なので、以下を返す。
(transform) => { return transform(1); };
そして、(one) => { return ID.unit(succ(one)); }
が適用される。
(one) => { return ID.unit(succ(one)); }(1)
よって元のモナドは、succ(1)
と同じ結果になる。
次に、flatMap
関数を入れ子にしたものをみていく。
これは、次の関数を合成する関数をおなじ働きをする。
const compose = (f, g) => { return (arg) => { return f(g(arg)); }; };
つまり、以下の等式が成り立つ。
ID.flatMap(ID.unit(1))((one) => { return ID.flatMap(ID.unit(succ(one)))((two) => { return ID.unit(double(two)) }) }) == compose(double, succ)(1)
これを順を追ってみていく。
まず、以下の関数に着目する。
ID.flatMap(ID.unit(succ(one)))((two) => { return ID.unit(double(two)) })
ID.flatMap
は以下を返す。
(transform) => { return transform(ID.unit(succ(one))); };
そして、(two) => { return ID.unit(double(two)) }
が適用される。
((two) => { return ID.unit(double(two)) })(ID.unit(succ(one)))
よって、元の以下の関数は、
ID.flatMap(ID.unit(1))((one) => { return ID.flatMap(ID.unit(succ(one)))((two) => { return ID.unit(double(two)) }) })
以下のようになる。
ID.flatMap(ID.unit(1))((one) => { return ((two) => { return ID.unit(double(two)) })(ID.unit(succ(one))) })
ID.flatMap
は、引数ID.unit(1) = 1
なので、以下を返す。
(transform) => { return transform(1); };
よって、以下の関数が適用されると、
(one) => { return ((two) => { return ID.unit(double(two)) })(ID.unit(succ(one))) }
最終的には以下のようになる。
(one) => { return ((two) => { return ID.unit(double(two)) })(ID.unit(succ(one))) }(1)
よって元のモナドは、関数を合成する関数と同じ働きをする。
const compose = (f, g) => { return (arg) => { return f(g(arg)) } } ID.flatMap(ID.unit(1))((one) => { return ID.flatMap(ID.unit(succ(one)))((two) => { return ID.unit(double(two)) }) }) == compose(double, succ)(1)