JavaScript でカリー化、再び
2008-02-14


以前、「JavaScript で引数束縛」において関数のカリー化を試みました。しかし、そこでカリー化された関数は、そのままでは一度しか部分適用ができず、また、最初の関数呼び出しは必ず部分適用として扱われていました。

function mean3(a, b, c) { return (a + b + c) / 3; }
// 「JavaScript で引数束縛」における curry 関数。
var curriedMean3 = curry(mean3);
curriedMean3(1)(2, 3); // => 2

curriedMean3(1)(2)(3);
// => TypeError: curriedMean3(1)(2) is not a function
// そのままでは部分適用を 2 回以上行えない。
// curry(curriedMean3(1))(2)(3) なら大丈夫。

curriedMean3(1, 2, 3);
// => function () { ... }
// 十分な数の引数が与えられているにもかかわらず、
// 部分適用とみなされて関数が返る。

そこで、この制限を取り除いた――すなわち、1 回カリー化するだけで複数回の部分適用が行え、十分な数の引数が与えられれば直ちに結果を返すような関数を返す――カリー化関数を作ってみようと思います。

このカリー化関数の名前を curry としましょう。curry 関数は、引数として関数を受け取り、その関数をカリー化した関数を返します。とりあえず、関数を受け取り関数を返すというのをコードにしてみます。

function curry(f) {
  return function () {
    ...
  };
}

返される関数の中では何を行えばいいのでしょうか。それはこの関数が呼び出されたときの引数の数によって異なってきます。十分な数の引数が与えられたなら、元の関数を呼び出し、その結果を返してやらなくてはいけません。ある関数 (Function オブジェクト) が必要とする引数の数は、その Function オブジェクトの length プロパティから得ることができるので、これを利用してやります。

function curry(f) {
  return function () {
    if (arguments.length >= f.length)
      return f.apply(null, arguments);
    ...
  };
}

これで「十分な数の引数が与えられたら直ちに結果を返す」ことは達成できました。引数の数が足りなかった場合は、部分適用がなされたものとみなして、関数を返すことにします。

function curry(f) {
  return function () {
    if (arguments.length >= f.length)
      return f.apply(null, arguments);
    return function () {
      ...
    };
  };
}

部分適用の結果として返ってくる関数とは何でしょうか。それは、足りない引数を受け取り、元々の関数を実行する関数です。これを実現するためには、部分適用された引数の値を覚えておく必要があります。

function curry(f) {
  return function () {
    if (arguments.length >= f.length)
      return f.apply(null, arguments);
    var args = Array.prototype.slice.call(arguments);
    return function () {
      return f.apply(null, args.concat(Array.prototype.slice.call(arguments)));
    };
  };
}

Array.prototype.slice.call(arguments) というのは Arguments オブジェクトを配列に変換するための決まり文句のようなものです。Array オブジェクトの slice メソッドは、配列の一部を抜き出し新たな配列として返しますが、引数を省略すると 0 番目から length - 1 番目までの要素を抜き出したもの、すなわち元の配列全体のコピーを返します。これを Arguments オブジェクトに適用することで、Arguments オブジェクト全体をコピーした配列が返ってくるというわけです。JavaScript 1.6 以降なら、Array generics により Array.slice(arguments) と書くこともできます。

しかし、このままでは相変わらず 1 回のカリー化につき 1 回の部分適用しかできません。複数回の部分適用ができるようにするためには、部分適用の結果として返ってくる関数も、部分適用ができるようにする必要があります。部分適用ができるようにするためにはどうすればいいか、そう、カリー化です。とりあえず 1 回の部分適用ができるようになるカリー化関数はできているのですから、それを再度適用してやればよいのです。


続きを読む

[JavaScript]

コメント(全1件)
コメントをする


記事を書く
powered by ASAHIネット