以下を見て私も作りたくなったので。

で、出来上がったのがこちら。

var curry = function(f){
  var fs      = f.toString();
  var op      = fs.indexOf('(');
  var cp      = fs.indexOf(')');
  var ob      = fs.indexOf('{');
  var cb      = fs.lastIndexOf('}');
  var args    = fs.substr(op+1,cp-op-1).split(/,\s*/);
  if (!args.length) return f; // 具がないのはそのまま返す
  var curried = new Function(args.pop(), fs.substr(ob+1,cb-ob-1));
  while (args.length){
    curried = new Function(args.pop(), 'return ' + 
                           curried.toString().replace(/\n/,''));
    // .toString().replace(/\n/,'') は Opera対策
  }
  return curried;
};

Firefox2.0.0.12, Safari 3.04, Opera 9.25 で動作確認済み。IEは読者まかせ:)

見ての通り、具(関数)を一端文字列としてバラして煮込み直している。考えとしては、

JavaScriptでカリー化 - 檜山正幸のキマイラ飼育記
うーん、テキストつぎはぎは、やっぱダサイな -- いいんか? これで。

に近いのだけど、eval()も正規表現も(split以外では)使わないところが隠し味。

ダサいといえばダサいのだけど、こうするには理由が3つある。

  1. 純粋にcurry化された関数が欲しかった。これはcurry(function(a,b,c){ return a+b+c })(1)(2)(3)のみをOKとし、curry(function(a,b,c){ return a+b+c })(1)(2,3)はいらない、という意味。
  2. curry化されたものを文字列に戻した時に、一目見てcurry化されたというのがわかるようにしたかった。例えばcurry(function(a,b,c){ return a+b+c }).toString()は、私のrecipeだと
    function anonymous(a) {
      return function anonymous(b) {
        return function anonymous(c) {
          return a + b + c;
        };
      };
    }
    

    となるけど、nanto版だと

    function (_0, _1, _2) {
      return f.apply(this, arguments);
    }
    

    となって原型を留めていない。

  3. 実行速度。nanto版は、部分適用の都度、関数を作り直しているので遅そう。

最後のものに関しては、ベンチマークを取ってみた。

function cook_curry(recipe, times){
  return function(){
    var spice = function(a,b,c){ return 1 };
    for (var curry = recipe(spice), i = 0; i < times; i++){
      for (var f = curry(i), j = 0; j < times; j++){
        for (var g = f(j), k = 0; k < times; k++){
          var h = g(k);
          if (h == 1) continue;
          alert('whoa!');
          return;
        }
      }
    }
  }
}

というやり方で、三重に調理した場合の時間を測るのである。

以下、実際に計測。

Run with 3 iterations:

203回のiterationで、いずれも20倍ぐらい差が出た。

これでHaskellとも勝負できる...か!?

Dan the Curry Cooker