JavaScriptのカリー化について味見してみる

More than 3 years have passed since last update.


JavaScriptのカリー化について味見してみる

JavaScriptには、「部分適用」や「カリー化」といった考え方が存在します。

ココらへんは定義がわかりづらかったり、筆者自身も完璧に理解できているとは言いがたい部分もありますので、ざっくりと全体像でもお伝えできればと思います。

関数の適用、部分適用、カリー化の順番でお話していきます。


関数の適用とは

そもそも、関数プログラミング言語においては、関数は「呼び出されるもの」というより、「適用されるもの」と捉えるほうが正確です。JavaScriptにはFunction.prototype.apply()メソッドがあります。

つまり、関数の呼び出しはapply()メソッドの糖衣構文(シンタックスシュガー)である、ということです。実際にapply()を使ってみます。

一つ目の引数には、関数の内部でthisに束縛されるオブジェクト、

二つ目の引数には、引数の配列で、関数の内部で使用したい値の配列を渡します。


var multiply = function (x, y){
return x * y;
};

multiply.apply(null, [4, 5]); // -> 20

一つ目の引数が異なる場合を紹介します。

名前空間myAppを用意し、その中のmultiply()を適用します。


var myApp = {
add: function(x, y){
return x + y;
},

multiply: function(x, y){
return x * y;
}
};

// 普通に呼び出す
myApp.add(4, 5);
myApp.multiply(4, 5);

// apply()で適用する
myApp.add.apply(myApp, [4, 5]);
myApp.multiply.apply(myApp, [4, 5]);

ここまでで、 「関数は実は呼び出すものではなく適用されるもの」 ということがわかったでしょうか?次のフェーズに進みます。


部分適用とは

関数の呼び出しには、実際には 「関数に引数の配列を適用する」 ことがわかりました。これがわかれば次は簡単です。「部分適用」とは、単純に、 「引数を全てではなく一部だけ渡す」 ことを指します。

改めて、2つの引数をとるmultiply()メソッドについて考えてみます。以下の例では、完全適用、部分適用の二パターンを紹介します。


// 完全適用
var multiply = function(x, y){
return x * y;
};

multiply.apply(null, [4, 5]);

// 部分適用
var partialMultiply = function(y){
return multiply(4, y);
}

partialMultiply(5); // -> 20

部分適用では、第一引数を固定しています。

部分適用によって、partialMultiply()という新しい別の関数が得られました。

そしてその関数は別の引数で呼ぶことができます。


カリー化とは

カリー化された関数に引数を渡すだけで、別の関数を簡単に定義できる。それがカリー化の醍醐味です。

部分適用とカリー化の違いは、

- カリー化: 関数を引数1つずつに分割して適用させる

- 部分適用: 一部の引数を固定して新しい関数を作り出すこと

です。もっというと、

「関数に部分適用を理解させ処理させること」 です。

先程の例では、以下のようにmultiply()メソッドの第一引数を「4」に固定していました。これは部分適用です。しかし、これでは汎用性が足りません。


// 部分適用
var partialMultiply = function(y){
return multiply(4, y);
}

partialMultiply(5); // -> 20

これをカリー化で表現すると、以下のようになります。


function multiply(x, y){
if(typeof y === "undefined"){ //部分適用
return function(y){
return x * y;
};
}
//完全適用
return x * y;
}

multiply(4)(5);

カリー化の特徴は、関数を呼び出す際に、引数をひとつずつ渡すようになっている

点です。これによって、「複数の引数を必要とする関数を、1つの引数の関数の定義だけで

同じ機能をもつ関数」に書き換えることが出来ました。

次に、引数が2つ以上の場合でも汎用的に使えるよう、multiply()

書き換えてみましょう。


function createCurry(func){
var slice = Array.prototype.slice,
stored_args = slice.call(arguments, 1);

return function(){
var new_args = slice.call(arguments);
args = stored_args.concat(new_args);
return func.apply(null, args);
};
}

出典: 『JavaScriptパターン』

このカリー化を使って、テストをしてみましょう。


function multiply(x, y){
return x * y;
}

// pattern A
var newMultiply = createCurry(multiply, 5);
newMultiply(4);

// pattern B
createCurry(multiply, 5)(4);

また、変換関数createCurry()のパラメータは複数でも使用可能です。

カリー化も複数段階で使うことができます。


function calcVolume(x, y, z){
return x * y * z;
}

// 複数の引数
createCurry(calcVolume, 5)(2, 3); // 30
createCurry(calcVolume, 5, 2)(3); // 30

// 2段階のカリー化
var calcVolumeWithWidthFive = createCurry(calcVolume, 5);
calcVolumeWithWidthFive( 2, 3 );
var calcVolumeWithWidthFiveAndHeightTwo = createCurry(calcVolumeWithWidthFive, 2);
calcVolumeWithWidthFiveAndHeightTwo( 3 );


参考リンク

特に一つ目の、KDKTNさんの食べられないほうのカリー化入門は非常にわかりやすいので、一読をオススメいたします。