HaskellのMonadをJavaScriptで実装するとしたら

論理面も怪しいし、利用してるAPIも適当なんでアレで動くものじゃないですが、大枠だけ書いてみるテスト。

参考はParsecですが、状態管理はだいぶ省略してます(特にエラー情報)。

あと以下のPPTは、関数型なパーザーがどういう仕組みかわかりやすいかもしれない

(function (){
  var PState = {
    source: "";
    index: 0;
    result: null;
  };
  
  var Parser = {
    parse: function (pstate) {
      return pstate;
    }
  };
  
  var Bind = function (src, dst) {
    var o = {};
    o.parse = function (pstate) {
      var nstate = src.parse(pstate);
      if (nstate == null) return null; // fail;
      return dst(nstate.result).parse(nstate);
    };
    return o;
  };
  var Return =  function (value) {
    var o = {};
    o.parse = function (pstate) {
      var nstate = pstate.clone(); // TBD
      nstate.result = value;
      return nstate;
    };
    return o;
  };
  var Fail = function () {
    var o = {};
    o.parse = function (pstate) {
      return null;
    };
    return o;
  };
  
  var Char = function (ch) {
    var o = {};
    o.parse = function (pstate) {
      if (pstate.source[pstate.index] == ch) {
        var nstate = pstate.clone();
        nstate.index = pstate.index + 1;
        nstate.result = ch;
        return nstate;
      } else {
        return null;
      }
    };
    return o;
  };
  
  var Many = function (parser) {
    var o = {};
    o.parse = function (pstate) {
      var current = pstate;
      var result = [];
      while (true) {
        var next = parser.parse(current);
        if (next != null) { 
          result.push(next.result);
          current = next;
        } else { // fail
          var nstate = current.clone();
          nstate.result = result;
          return nstate;
        }
      }
    };
    return o;
  };
  
  var OrElse = function (p1, p2) {
    var o = {};
    o.parse = function (pstate) {
      var p1state = p1.parse(pstate);
      if (p1state == null) return p2.parse(pstate);
      else return p1state;
    };
    return o;
  };
  
  // ex
  abcParser = Bind(Char('a'), function (a) {
    return Bind(Char('b'), function (b) {
      return Bind(Char('c'), function (c) {
        return Return(a + b + c);
      });
    });
  });
  
  print(abcParser.parse("abc").result); // "abc"
  print(abcParser.parse("abcd").result); // "abc"
  print(abcParser.parse("abd")); // null
  print(abcParser.parse("ab")); // null
})();

でかいw。

PStateってのは状態です。ここには適当に必要そうなものを入れる。ただし、引数や戻り値は絶対に上書きしてはいけません(受け渡しの前にコピーすれば、気にしなくていいけど)。


ParserってのがMonad型の例です。メソッドは、実際に起動するparseを用意。そしてMonadの基本のBind、Return、Failを用意。各種parserはparseメソッドを上書きしていきます。終端パーザーとしてChar、組合せパーザーとしてManyとOrElseを用意。


仕組み的には、

  • 関数呼び出しではParserオブジェクト(parseメソッドを持ったオブジェクト)を返す。
  • そしてparseメソッドで初めて計算する。parseメソッドは組み立て時には(普通は)呼ばず、組み立て後に呼ぶ。
  • パーザー組み立てでは、Bindなどを使って組み立てていく

というものになる。


ただ、このままだと組合せパーザーを書くのも正直一苦労するけど。