2015年5月20日水曜日

Maybe モナド

エラーを扱う

MaybeモナドはStateモナドに比べるとやりたいことはシンプルだ。

ある関数fに入力がある範囲なら処理結果を出力させそれを後続の関数gの入力とし、範囲外ならエラーとして処理を打ち切りたいとする。



普通に考えればせっかくタプルがあるのだから、片方に結果を入れて、もう片方に結果が有効かを示す情報を格納すればよい。さすがに関数型でも手続き型でも後続の関数gの中で入力エラーをチェックするのはナンセンスなので、fgの接合部分でエラーチェックをして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 件のコメント:

コメントを投稿