経験は何よりも饒舌

10年後に真価を発揮するかもしれないブログ 

「関数型プログラミングの基礎」モナドを作るを理解する

wafuwafu13.hatenadiary.com

と同じ本の、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関数を介して1succ関数を適用する処理は、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)

モナドの初歩が理解できたので、この後出てくるMaybeモナドやIOモナドの理解もしやすくなるはず。