JavaScriptで関数をカリー化する方法

カリー化された関数は、JavaScriptコードの可読性と表現力を高めるのに役立ちます。複雑なロジックをより小さく、自己完結的で、管理しやすいコードに分解したい場合に、カリー化手法は理想的です。

JavaScriptのカリー化された関数、関数カリー化の手法を使用して部分適用関数を作成する方法、およびカリー化された関数と部分適用関数の両方の実生活のユースケースについてすべて学びましょう。

カリー化とは何ですか?

カリー化は数学者のハスケル・B・カリーにちなんで名付けられ、その概念はラムダ計算から派生しています。カリー化は、複数のパラメーターを受け取る関数を取り、それを一連の単項(1パラメーター)関数に分解します。言い換えると、カリー化された関数は一度に1つのパラメーターのみを受け取ります。

カリー化の基本的な例

以下は、カリー化された関数の例です。

function buildSandwich(ingredient1) {return (ingredient2) => {return (ingredient3) => {return ${ingredient1},${ingredient2},${ingredient3}}}}

buildSandwich()関数は、ingredient2引数を受け取る別の関数(匿名関数)を返します。次に、この匿名関数は、ingredient3を受け取る別の匿名関数を返します。最後に、この最後の関数は、JavaScriptで文字列をフォーマットする方法であるテンプレートリテラルを返します。

作成したのは、各関数がその下にある関数を呼び出すネストされた関数であり、最後に到達するまで続きます。これで、buildSandwich()を呼び出して単一のパラメーターを渡すと、まだ提供していない引数がある関数の一部が返されます。

console.log(buildSandwich("Bacon"))

出力から、buildSandwichが関数を返すことがわかります。

関数の呼び出しを完了するには、3つの引数をすべて指定する必要があります。

buildSandwich("Bacon")("Lettuce")("Tomato")

このコードは、「ベーコン」を最初の関数に、「レタス」を2番目の関数に、「トマト」を最後の関数に渡します。言い換えると、buildSandwich()関数は実際には3つの関数に分割され、各関数は1つのパラメーターのみを受け取ります。

従来の関数を使用してカリー化することは完全に有効ですが、ネストが深くなるほど、すべてのネストがかなり見苦しくなる可能性があります。これを回避するには、矢印関数を使用して、よりクリーンな構文を利用できます。

const buildMeal = ingred1 => ingred2 => ingred3 =>${ingred1}, ${ingred2}. ${ingred3};

このリファクタリングされたバージョンはより簡潔で、通常の関数と比較して矢印関数を使用する利点があります。以前と同じように関数を呼び出すことができます。

buildMeal("Bacon")("Lettuce")("Tomato")

部分適用カリー関数

部分適用関数は、カリー化の一般的な使用例です。この手法では、一度に必要な引数のみを指定します(すべての引数を指定するのではなく)。必要なすべてのパラメーターを渡して関数を呼び出すたびに、その関数を「適用」したと言います。

例を見てみましょう。

const multiply = (x, y) => x * y;

以下はmultiplyのカリー化されたバージョンです。

const curriedMultiply = x => y => x * y;

curriedMultiply()関数は、最初の関数のx引数と2番目の関数のyを受け取り、次に両方の値を掛けます。

最初の部分適用関数をを作成するには、curriedMultiple()を最初の引数で呼び出し、返された関数を変数に代入します。

const timesTen = curriedMultiply(10)

この時点で、コードはcurriedMultiply()関数を「部分適用」しています。そのため、timesTen()を呼び出すときはいつでも、1つの数値を渡すだけで、その数値は自動的に10(適用された関数内に格納されている)と掛けられます。

console.log(timesTen(8)) // 80

これにより、単一の複雑な関数に基づいて複数のカスタム関数を構築し、それぞれに独自の機能をロックできます。

実際のWeb開発のユースケースに近い例を見てみましょう。以下に、最初の呼び出しで要素のid、2番目の呼び出しでコンテンツを受け取り、指定されたidとコンテンツに基づいて要素を更新するupdateElemText()関数があります。

const updateElemText = id = content=> document.querySelector(#${id}).textContent = content// Lock the element's id into the function:const updateHeaderText = updateElemText('header')// Update the header textupdateHeaderText("Hello World!")

カリー化関数による関数合成

カリー化のもう1つの一般的な使用例は関数合成です。これにより、特定の順序で小さな関数を呼び出し、それらを1つのより複雑な関数にまとめることができます。

たとえば、仮説的なeコマースWebサイトでは、次の3つの関数を正確な順序で1つずつ実行したい場合があります。

const addCustomer = fn => (...args) => {console.log("顧客情報を保存しています")return fn(...args)}const processOrder = fn => (...args) => {console.log(注文 #${args[0]} を処理しています)return fn(...args);}let completeOrder = (...args) => {console.log(注文 #${[...args].toString()} が完了しました。);}

このコードはletキーワードを使用してcompleteOrder()関数を定義していることに注意してください。これにより、変数に値を再代入することができ、これはJavaScriptのスコープの仕組みの一部です。

次に、顧客を最初に追加したいので、関数を逆順(内側から外側)で呼び出す必要があります。

completeOrder = (processOrder(completeOrder));
completeOrder = (addCustomer(completeOrder));
completeOrder("1000")

これにより、次の出力が得られます。


上記の関数を通常の方法で記述すると、コードは次のようになります。

function addCustomer(...args) {
return function processOrder(...args) {
return function completeOrder(...args) {
// end
}
}
}

addCustomer()関数を呼び出して引数を渡すと、内側から始めて関数の先頭まで進みます。

カリー関数を使用して通常の関数をカリー化された関数に変換する

カリー化された関数をたくさん使用する予定がある場合は、ヘルパー関数を使用してプロセスを効率化できます。

この関数は、通常の関数をカリー化された関数に変換します。これは再帰を使用して任意の数の引数を処理します。

const curry = (fn) => {
return curried = (...args) => {
if (fn.length !== args.length) {
return curried.bind(null, ...args)
}
return fn(...args);
}
}

この関数は、複数の引数を受け取る標準的に記述された関数をすべて受け入れ、その関数のカリー化されたバージョンを返します。実際に使ってみるには、3つのパラメーターを受け取ってそれらを合計する次のサンプル関数を使用します。

const total = (x, y, z) => x + y + z

この関数を変換するには、curry()関数を呼び出してtotalを引数として渡します。

const curriedTotal = curry(total)

これで関数を呼び出すには、すべての引数を渡すだけです。

console.log(curriedTotal(10)(20)(30)) // 60

JavaScriptの関数についてさらに詳しく

JavaScriptの関数は非常に柔軟であり、関数のカリー化はその一部にすぎません。矢印関数、コンストラクタ関数、匿名関数など、他にも多くの種類の関数があります。これらの関数とそのコンポーネントに慣れることは、JavaScriptを習得するための鍵です。