概要
今回の記事では、プログラミングにおける関数設計の際にあまり注目されない概念である"Currying"(カリー化)について取り上げることにしました。wikipediaで「カリー化」を調べると、
複数の引数をとる関数を、引数が「もとの関数の最初の引数」で戻り値が「もとの関数の残りの引数を取り結果を返す関数」であるような関数にすること(あるいはその関数のこと)である。
という説明が出てきます。この説明では混乱する人も多いと思うので、よりわかりやすく解説をしていこうと思います。
Curry 【意味】他変数関数を「1変数関数の連鎖」に変換すること
Curryingとは
そもそも、皆さんはJavaScriptにおける関数の位置付けをどのくらい理解しているでしょうか? “First Class Citizen in JavaScript"のように検索すると、JavaScriptにおいてFunctionはFirst Class Citizenであるという記事がいくつも出てくるかと思います。ここでは詳しいことについては割愛しますが、
- 変数として扱うことができる
- 関数の引数として扱うことができる
- 関数の返り値となることができる
という3つの性質を持つもののことをFirst Class Citizenと定義しており、Functionはその定義を満たす存在としています。Curryingはこのような関数に対する特有の文法であるため、見る価値のあるものだと思います。
理解のために具体例から見ていきましょう。
まず、ある2つの値を代入するとその和を返す関数について考えます。この関数は普通次のように書くかと思います。
function sum1(a, b) {
return a + b
};
sum1(3, 4); // 7
これをCurryingして書くと、次のようになります。
function sumCarry1(a) {
return function(b) {
retrun a + b
}
};
sumCarry1(5)(6); // 11
ここでsumCarry1がしていることは、「sumCarry1に代入した引数(a)を定数として持っている関数を返すこと」になります。例えば、aに10を代入した新しい関数sumCarry2は次のように書くことができます。
const sumCarry2 = sumCarry(10);
つまり、ここで新しく定義したsumCarry2関数は、
function sumCarry2(b) {
return 10 + b
};
と定義した関数と同じ関数を表すということです。ここで出てきた関数の使用例と結果は次のようになります。
console.log(sum1(1, 2)); // 3
console.log(sumCarry1(3)(4)); // 7
console.log(sumCarry2(5)); // 15
アロー関数でのCurrying
アロー関数ではより簡潔にCurryingすることができます。
const sumArrowCurry1 = a => {
return b => {
return a + b
}
};
console.log(sumArrowCurry1(6)(7)); // 13
さらにreturnを省略してより簡潔に表現することもできます。
const sumArrowCurry2 = c => d => c + d;
console.log(sumArrowCurry(8)(9)); // 17
Curryingと部分適用
ここまでの例で紹介してきたような使い方ではイマイチCurryingを使うメリットは感じられないと思います。
この節では、簡単かつメリットが見えるような使い方の例を見ていきましょう。
// 非カリー化関数
function individualInfo(name, state, message) {
return `${name} is a ${state}, ${message}.`
};
console.log(individualInfo("John", "high school student", "and goes to school every day"));
// John is a high school student, and goes to school every day.
このような関数を考えた時、個人ごとに引数を変えて使うのは非常に不便であるように思われます。そこでこれをCurryingしてみましょう。
const individualInfo2 = state => message => name =>
`${name} is a ${state}, ${message}.`
//中学生に適用する関数を作る
const jhsInfo = indivivualInfo2("junior high school student");
//医者に適用する関数を作る
const doctorInfo = indivivualInfo2("doctor");
//勤勉な医者に対する関数
const diligentDoctor = doctorInfo("and works diligently every day");
//ヤブ医者に対する関数
const quackDoctor = doctorInfo("but has a bad reputation");
console.log(diligentDoctor("Michael"));
// Michael is a doctor, and works diligently every day.
console.log(quackDoctor("Beth"));
// Beth is a doctor, but has a bad reputation.
実際にはこのように使われることはほぼないと思いますが、この例によってCurryingについての理解を深めることができるかと思います。
次に部分適用の概念について紹介します。
部分適用【意味】 カリー化された関数に対して、一部の引数を固定した別の関数を生成すること。
本記事をここまで読めば、部分適用の意味も理解できるでしょう。すなわち、上の例ではindividualInfo2関数はカリー化された関数であり、この関数の一部の引数を固定したjhsInfo関数、doctorInfo関数、diligentDoctor関数、quackDoctor関数が部分適用した関数というわけです。
まとめ
本記事では、関数のCurryingについて紹介してきました。複数の引数を分離した扱うことができるようにすることで、最後に紹介した部分適用の考え方を使えば、使い勝手の良い関数を新しく定義することもできるでしょう。
しかしながら一方で、Curryingは可読性を下げるといった一面もあります。Curryingによる冗長性の低減と可読性の低減の間で、工夫した関数設計が求められそうです。