エラーを扱う
MaybeモナドはStateモナドに比べるとやりたいことはシンプルだ。ある関数fに入力がある範囲なら処理結果を出力させそれを後続の関数gの入力とし、範囲外ならエラーとして処理を打ち切りたいとする。
普通に考えればせっかくタプルがあるのだから、片方に結果を入れて、もう片方に結果が有効かを示す情報を格納すればよい。さすがに関数型でも手続き型でも後続の関数gの中で入力エラーをチェックするのはナンセンスなので、fとgの接合部分でエラーチェックをしてgを呼び出すか処理を終了するか決めるのが自然だ。
そうするのが自然なのだが、Cでドライバのコード書いた経験があるならわかると思うが、この手のエラーを出力するかもしれない関数を並べると、エラーチェックのためのif文が大量に並んで非常にコードが見づらくなる。普通の神経の人ならぶち切れてマクロを定義して無理矢理1行に納めるが、それでも見栄えはよくない。
C++やJavaのように例外が扱えるとこの手のコードはきれいに書けるが、関数型言語では例外は扱わないようだ。本では例外が危険だからと書いてあったが、個人的には前述の「制御を分割して部品化する」といった都合上、例外が扱いにくい(下位にどんな関数が来るかわからないし、制御を分割しているのでどこで例外を待てばいいのか決めにくい)仕様になっているんじゃないかと思っている。
そこで、Maybeモナドの出番となる。
2つのbind関数
ListモナドとStateモナドはそれぞれ"List", "State"のキーワードによりバインド関数が生成されるが、Maybeモナドでは1つのモナドで"Just"と"Nothing"の2つのキーワードを持っている。(逆にMaybeのくせに"Maybe"ではバインド関数は生成されない)下の図のように、Justの場合はバインドされた関数gを取り込んでfの戻り値を渡すbind関数を生成する。Nothingの場合はバインドされた関数は無視してそのままNothingを返すbind関数を生成する。
最終的な出力にはNothingを含むのでこれもMaybeモナドとなり、この後にさらに別の関数をバインドしていってもNothingの場合はひたすらNothingが伝搬していくことになる。
理屈がわかったところでこれまで同様JavaScriptで書いてみる。
var Maybe = {
return: function(a) {
return Maybe.Just(a)
},
Just: function(a) {
return {
bind: function(f) {
return f(a);
},
inner: a
}
},
Nothing: function() {
return {
bind: function() {
return Maybe.Nothing();
},
inner: 'Nothing'
}
}
}
var f = function(a) {
if(a < 0) return Maybe.Nothing();
else return Maybe.Just(a * a);
}
var g = function(a) {
return Maybe.Just(a * 2);
}
document.write(f(1).bind(g).inner+'<br>');
document.write(f(-1).bind(g).inner+'<br>');
fの引数が0以上ならJustにより生成されるbind関数でfの結果がgに渡される。0未満ならNothingにより何もせずにNothingを返すbind関数が生成され、Nothingが継承されていく。
Maybe.JustやMaybe.Nothingはなんか語呂が悪いので、Maybeの外に出すついでにinnerではなくvaluOfを利用するとすっきり書ける。
もちろん前回のdo記法もどきもちゃんと使える。
var Maybe = {
return: function(a) {
return Just(a)
}
}
var Just = function(x) {
return {
bind: function(f) {
return f(x);
},
valueOf: function() {
return x;
}
}
}
var Nothing = function(x) {
return {
bind: function() {
return Nothing();
},
valueOf: function() {
return 'Nothing';
}
}
}
var f = function(a) {
if(a < 0) return Nothing();
else return Just(a * a);
}
var g = function(a) {
return Just(a * 2);
}
document.write((function() {
var a, b;
return MonaDo(f(2),
[
function(x_) { a = x_; return g(2); },
function(x_) { b = x_; return Maybe.return(a + b); }
]);
}())+'<br>');
0 件のコメント:
コメントを投稿