関数を繋ぐ
読んだ本にはモナドとは「文脈を持つ計算を扱う」ための仕組みとあるが、何を言っているのかさっぱりわからない。ネットでもいろいろ勉強した結果、今のところの自分の理解だとモナドは関数を繋げるためのものだと解釈している。(間違っているかもしれないが)
単に関数を繋げるだけならHaskellにはすでに前回のCombineに相当する演算子が用意されている。これを使えば、単純に「
g . f x
」と書けば「
g(f(x))
」と等価な処理に変換してくれる。この場合は、単純に下図のように関数
f
の出力をそのまま関数g
の入力として利用する。
しかしながら、必ずしも毎回出力と入力が一対一対応するとは限らない。時には間に変換を挟んだりする必要が出てくる。そこでモナドが登場する。
イメージ的には下図のように先行の関数は出力の値を含んだモナドを出力して、モナドは必要により値を変換して後続の関数に渡す。
具体的な動きについて例としてListモナドを利用して見てみる。モナドの例としてはMaybeモナドがよく挙げられているが、Maybeモナドは動きがトリッキーなので個人的にはListモナドの方が入りやすいと思う。
Listモナド
Listモナドでは配列の要素を入力として配列を返す関数を想定している。例えば、値aを入力として、それを[a,a]という2要素の配列に変換する関数fがあったとして、これを前回のmapを利用して配列の各要素に適用すると、
f(1) → [1, 1]
map(f)([1,2,3]) → [[1,1],[2,2][3,3]]
これの出力を別の関数gに同じくmapで適用するためには、入れ子を外して
[1,1,2,2,3,3]
Listモナドはこのmapと変換を一括してfとgの接続部分で行ってくれる。個人的にはこの変換に需要があるかはよくわからないが、Listモナドが用意されていると言うことは関数型言語の世界ではそれなりに需要があるのだろう。
Listモナドの挙動を理解するために、試しにJavaScriptで正確かどうかはわからないがそれっぽい挙動をするコードを書いてみた。
var List = function(array) {
return {
// (>>=) = concatMap に相当
bind: function(f) {
return List(concatMap(f)(array));
},
// 中身の配列を取得
inner: array,
}
}
// return = (:[]) に相当
List.return = function(a) {
return List([a]);
}
var concatMap = function(f) {
return function(array) {
var newArray = [];
for(var i = 0; i < array.length; i++) {
var tmp = f(array[i]);
newArray = newArray.concat(tmp.inner);
}
return newArray;
}
}
これで、f,gを次のように定義して適用すると、期待通り入れ子を外した配列が出来上がる。
var f = function(a) {
return List([a, a+1]);
}
var g = function(a) {
return List([a, a*3]);
}
// f >>= g に相当
document.write( f(1).bind(g).inner + '<br>' );
自分の理解で作ったJavaScriptなのでHaskellと同じことになっているかはわからないが、これからListモナドの動作を見ていくと、まずListモナドでは前の関数の出力aを持ったbind関数を生成する。bind関数では右辺に来る関数を受け取ってこれと持っているaを組み合わせて出力(モナド)を生成する。
絵的に書くと下のようになるだろうか。
Listの場合はモナドを使わずにconcatMapだけで繋げることも出来るが、この場合は
concatMap(g)(concatMap(f)(1))
とconcatMapの方が目立ってしまうので、関数型言語ではモナドを使うことで間の変換処理を隠蔽してfとgの関係性を見えやすくしたということだと思う。
ちなみに、うえの説明ではわかりやすさのためにListオブジェクトにbind関数をぶら下げたが、Array.prototypeに追加することでよりシンプルに使えるようになる。
Array.prototype.bind = function(f) { return concatMap(f)(this); } var List = { return: function(a) { return [a]; } } var f = function(a) { return [a, a+1]; } var g = function(a) { return [a, a*3]; } document.write( f(1).bind(g)+'<br>
' );
モナドの大まかな挙動がわかったところで、今後はより複雑なMaybeモナドとStateモナドについても理解していく。
0 件のコメント:
コメントを投稿