やわらかテック

業務を通して得られた知見、知見。個人的に試してみたこと、興味のあることについてアウトプットしています。

【JavaScriptで解説】部分適用って何?カリー化との違いは?

部分適用について🤔

部分適用とは一言でいえば、「関数の引数を一部だけ固定して、新しい関数を作るための仕組みの総称」です。

よく混同されるカリー化とは別の言葉で、カリー化よりも広い意味を扱います。カリー化は部分適用を行う方法の1つです。その他にも部分適用を行う方法はいくつかあります。

部分適用を行う2つの方法✒️

カリー化

例として以下の関数を使用します。この関数は2つの引数を受け取り、受け取った2つの値を足し合わせます。

const addN = (n, m) => n + m; 
console.log(addN(1, 1)); // 2
console.log(addN(1, 10)); // 11

このような複数の引数を受け取る関数を、関数から関数を返すことで1つずつ引数を受け取るように分割することが出来ます。
この手法をカリー化と呼びます。

const addN = (n) => (m) => n + m;
// [Function: addN]

引数を1つ与えて実行してみて、返ってくる値を確認してみます。

const addN = (n) => (m) => n + m;
const add1 = addN(1);
console.log(add1); // [Function (anonymous)]

関数として値が返ってきていることが分かります。この時点でadd1nとして定義された引数の受け取りが完了して(m) => n + mという関数を値として返しています。

このadd1は関数なので、さらに引数を渡して実行することが出来ます。

const addN = (n) => (m) => n + m;
const add1 = addN(1);
const result = add1(10);
console.log(result); // 11


複数の引数を受け取る関数を、関数から関数を返すことで1つずつ引数を受け取るように分割すること

これがカリー化です。渡す値を変えれば様々なバリエーションの関数を定義することが出来ます。

const addN = (n) => (m) => n + m;
const add10 = addN(10);
const add100 = addN(100);
const add1000 = addN(1000);

console.log(add10(1)); // 11
console.log(add100(1)); // 101
console.log(add1000(1)); // 1001

また引数の数には上限はないので、分割をしまくれば、いくらでもカリー化をすることも出来ます。

const add5times = (a) => (b) => (c) => (d) => (e) => a + b + c + d + e;
const result = add5times(1)(1)(1)(1)(1);
console.log(result); // 5


定数を用いる(ラッパー関数を定義する)

もう1つの方法として関数をラップした別の関数を定義することでも部分適用をすることが出来ます。例として2つの引数を持つaddN関数をラップして、10を固定で足し合わせる関数を作成してみます。

const addN = (n, m) => n + m;
const add10 = (m) => addN(10, m);
console.log(add10(1)); // 11

引数の一部を固定化することで、新たな関数を定義することが出来ました。これも同じように部分適用がされていると判断出来ます。

このラッパー関数はカリー化と同じようにいくらでも作ることが出来ます。

const addN = (n, m) => n + m;
const add10 = (m) => addN(10, m);
const add100 = (m) => addN(100, m);
const add1000 = (m) => addN(1000, m);

console.log(add10(1)); // 11
console.log(add100(1)); // 101
console.log(add1000(1)); // 1001

以上です。

おまけ / 第一級関数について

実は今回紹介したカリー化が出来る言語と出来ない言語があります。その違いが第一級関数をサポートしているかどうかということです。第一級関数とは簡単に言えば、「関数を値として扱うことが出来るかどうか」という点です。

先ほど紹介した例では、関数を変数に代入していました。これは関数を値として扱うことが可能でなければ出来ないことです。

const addN = (n, m) => n + m;

また、関数を引数に渡したり、関数から関数を返すというのも関数を値として扱っているからです。

[1,2,3].map((n) => n + 10); // 関数を引数に渡している
const addN = (n, m) => n + m; // 関数から関数を返している

すなわちJavaScript第一級関数をサポートしていると言えます。