JavaScriptでカリー化を理解する
完全適用と部分適用
完全適用(full application)はいわゆる関数です。部分適用(partical application)は、引数の一部を渡すことをいいます。部分適用である関数add4は第1引数は固定の値で、第2引数のみ渡しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 完全適用 var add = function(a, b){ return a + b; }; add.apply(null, [4, 5]); // 部分適用 var add4 = function(a){ return add(4, a); } console.log(add4(5)); // -> 9 |
ちなみに、Javaでは、同じ名前であるが引数が異なる関数名(メソッド名)は宣言することができますが、JavaScriptは同じ関数名を設定することはできません。function式の場合は変数名が重複しますし、function文の場合は後勝ちとなるようです。
1 2 3 4 5 6 7 8 9 10 |
function hoge(a, b){ console.log('2 args'); } function hoge(a, b, c){ console.log('3 args'); } hoge(); // -> 3 args |
以下はfunction文の記述の順番を入れ替えてみた場合。実行されている関数が変わっているのがわかりますね。
1 2 3 4 5 6 7 8 9 10 |
function hoge(a, b, c){ console.log('3 args'); } function hoge(a, b){ console.log('2 args'); } hoge(); // -> 2 args |
ちょっと話が逸れましたが、完全適用と部分適用は以上です。
カリー化
カリー化とは、Wikipediaによると以下。
カリー化 (currying, カリー化された=curried) とは、複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
https://ja.wikipedia.org/wiki/%E3%82%AB%E3%83%AA%E3%83%BC%E5%8C%96
更に、以下のサイトではもう少しわかりやすく、このように記載されています。
「カリー化する」という用語は、すくなくとも以下の3つの意味で使われているようだ。
- 部分適用という意味
- これは明らかに間違い
- 「複数の引数を取る関数」を「一引数を取る関数のチェインに直す」こと
- これはkmizuさんの定義。世間でもよく使われる。
- 「構造体を一つ取る関数」を「構造体のメンバーを複数の引数にばらし、一引数を取る関数のチェインに直す」こと
「部分適用」の意味で使うのは明らかに間違いのなで排除。
サンプルとして作成した、カリー化されたコードを確認してみます。以下は足し算のコードです。
1 2 3 4 5 6 7 8 9 10 |
// currying var add = function (a) { return function (b) { return a + b; }; } var result = add(1)(2); console.log(result); |
add関数は無名関数を返します。無名関数は「(2)」を引数として実行され、a+bの足し算が行われ、結果が戻り値としてresultに格納されます。定義2の「引数を取る関数のチェイン」となっているのでカリー化されていますね。
カリー化のメリット
ではカリー化のメリットは何でしょうか。それは、部分適用で、例えば以下のmap関数(関数fと配列を与えると、配列全てに関数fを適用する)に先程作成したadd関数をそのまま使用できます。
1 2 3 4 5 6 7 |
var map = function (f, ar) { var ret = []; for (var i = 0; i < ar.length; i++) { ret[i] = f(ar[i]); } return ret; } |
先程作成したadd関数を含めて、実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// currying var add = function (a) { return function (b) { return a + b; }; } var map = function (f, ar) { var ret = []; for (var i = 0; i < ar.length; i++) { ret[i] = f(ar[i]); } return ret; } console.log(map(add(1), [3,4,5])); // -> [4, 5, 6] |
配列[3,4,5]が全て1加算されて[4,5,6]という値となりました。カリー化されていないadd関数ではこの方法で記述することはできず、forループを記述して配列を操作するindex変数を回して、、、とリーダブルで無いコードとなっていたでしょうね。
以前紹介したlodash.jsですが、lodashの第二引数に今回作成したカリー化された関数を与えることができます。
1 2 3 4 5 6 7 8 9 |
var add = function (a) { return function (b) { return a + b; }; } var array = [1, 2, 3]; var result = _.map(array, add(1)); console.log(result); |
カリー化を理解すると、既存のカリー化された関数とlodash.jsを組み合わせての開発が捗りそうです。