モナド

0と正の自然数からなる集合、[0, 1, 2, .. ] には2項演算 (+) があり次の二つの法則を満たしている。

1. 結合法則: (a + b) + c = a + (b + c)
2. 単位元の存在: 0 + a = a + 0 = a

このような集合をモノイドという。このようなモノイドでは、結合法則のおかげで、次のような計算も基本的な二項演算に式の変形をすることができる。

(a + b) + (c + d) = a + (b + (c + d))

また、こうやって得られた結果もやはり、モノイド集合の要素になる。(+) の演算はモノイドの集合内で完結している。

上のモノイドは数の集合についての話だが、これを、関数の集合に拡張したものだモナドだ。

集合X = [0, 1, 2, .. ]上の関数で戻り値としてXの要素をかえす関数 y = f(x), x ∈ X, y ∈ X を考えてみる。y = x + x とか、y = x * x とかいろいろ考えられるが、それらの関数をすべて集めた集合を考えることができる。[f(x), g(x), h(x), .. ] がそれらの関数の集合とすると、これらの関数の合成関数をつくる演算子 >>= は、関数を引数とする2項演算子だ。つまり、f(x) >>= g(x) -> h(x) となる。

演算子 >>= がモナドの演算子となるためには、この演算子が結合法則を満たさなければならない。つまり、

(f(x) >>= g(x)) >>= h(x) = f(x) >>= (g(x) >>= h(x))

がせいりつするように定義されていなければならない。また単位元の存在も必要だ。つまり、

1(x) >>= f(x) = f(x) >>= 1(x) = f(x)

上の単位元は、左単位元 1(x) >>= f(x) と右単位元 f(x) >>= 1(x) に分けることができる。

ところで、Haskell のモナドの三基本則の1は return が左単位元であることを言っており、2は return が右単位元であること、3は関数の合成規則である bind (>>=) が結合法則を満たしている事を示している。

1. (return x) >>= f == f x
2. m >>= return == m
3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)

こう考えると、Maybe などのモナド型のデータの集合を引数とする関数が >>= 演算子の引数であることも理解できる。たとえば上の原則の1の式は、(return >>= f) x = f x と言う風にも書くことが出きるので、return が 左単位元であることがわかりやすい。

このようにモナドの構造は、モノイドの構造と同値なので、モナドの振る舞いを、[0, 1, 2, .. ] と (+) 演算のふるまいになぞらえて考えることができる。

このように関数の集合をモナドとしてとらえることで、関数の結合の方法をモナド内に完結させることができる。Haskell のプログラムの中に、様々なモナドの世界をつくることによって、副作用のある関数を隔離したり、特定のデータを扱う処理をモナドとしてモジュール化したりすることが、できるようになる。また、モナドの演算子を定義すれば、モナド内の様々な処理をデータの中身を考えずに関数の演算子によるつなぎ合わせて処理することができる。

モナドを圏論の見かたで理解することはかなり垣根が高いので、自分なりの受け取りかたで使ってしまいがちになるが、本質を理解して使うとすっきりしたプログラムをかけるようになるのだろう。

管理人自身が圏論にはお手上げなので偉そうな事は言えないが、モナドのようなものがプログラム言語の仕様として採用されているところに、Haskell の奥深さがあるのだろう。とりつきやすさと、理解を拒否する深さとが両立しているところも魅力なのかもしれない。
[PR]
by tnomura9 | 2009-08-27 22:31 | Haskell | Comments(0)
<< モナドとの付き合い方。 モノイド >>