関数型プログラミングの原則は、多くの言語でサポートされています。 その中にはJavaScript、Haskell、Clojure、Erlangがあります。 関数型プログラミングメカニズムの使用は、とりわけ、純粋な関数、カリー化関数、高次関数などの概念の知識を意味します。
私たちが今日翻訳している資料はカレーについてです。 カリー化の仕組みと、このメカニズムの知識がJS開発者にとってどのように役立つかについて説明します。
カレーとは何ですか?
関数型プログラミングのカリー化とは、多くの引数を持つ関数を、1つの引数を持つネストされた関数のセットに変換することです。 1つの引数を渡してカリー関数を呼び出すと、次の引数が到着することを期待する新しい関数が返されます。 カリー化された関数が呼び出されるたびに、関数が必要な引数をすべて受け取るまで、次の引数を待つ新しい関数が返されます。 先に取得した引数は、クロージャーメカニズムのおかげで、関数が計算を実行するために必要なすべてのものを取得する瞬間を待っています。 最後の引数を受け取った後、関数は計算を実行し、結果を返します。
カリー化と言えば、これはいくつかの引数を持つ関数をアリティの少ない関数に変換するプロセスであると言えます。
アリティは、関数の引数の数です。 たとえば、以下は関数のペアの宣言です。
function fn(a, b) { //... } function _fn(a, b, c) { //... }
fn
関数は2つの引数(これはバイナリまたは2進関数です)を取り、
_fn
関数は3つの引数(3項、3項関数)を取ります。
カリー化中に、いくつかの引数を持つ関数が、それぞれが1つの引数を取る関数のセットに変換される状況について説明しましょう。
例を考えてみましょう。 次の機能があります。
function multiply(a, b, c) { return a * b * c; }
3つの引数を取り、その積を返します。
multiply(1,2,3); // 6
次に、それを一連の関数に変換する方法を考えてみましょう。各関数は1つの引数を取ります。 この関数のカリー化バージョンを作成し、いくつかの関数を呼び出すときに同じ結果を得る方法を見てみましょう。
function multiply(a) { return (b) => { return (c) => { return a * b * c } } } log(multiply(1)(2)(3)) // 6
ご覧のように、ここでは3つの引数を持つ単一の関数への呼び出しを変換します-
multiply(1,2,3)
を3つの関数への呼び出しに変換します-
multiply(1)(2)(3)
。
1つの機能が複数の機能に変わったことがわかります。 新しい構造を使用する場合、最後の関数を除く各関数は、計算の結果を返し、引数を取り、別の関数を返します。引数を受け入れて別の関数を返すこともできます。
multiply(1)(2)(3)
の形式の構造があまり明確に見えない場合は、この形式で記述して、これをよりよく理解してください。
const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result); // 6
ここで何が起こっているのかを行ごとに見てみましょう。
最初に、引数
1
を
multiply
関数に渡します。
const mul1 = multiply(1);
この機能が機能すると、この設計は機能します。
return (b) => { return (c) => { return a * b * c } }
これで、
mul1
は引数
b
をとる関数への参照があります。 関数
mul1
を呼び出して、
2
を渡します。
const mul2 = mul1(2);
この呼び出しの結果、次のコードが実行されます。
return (c) => { return a * b * c }
mul2
には、たとえば次の操作の結果として、
mul2
に含まれる可能性のある関数への参照が含まれます。
mul2 = (c) => { return a * b * c }
mul2
関数を呼び出して
3
を渡すと、関数は引数
a
および
b
を使用して必要な計算を実行します。
const result = mul2(3);
これらの計算の結果は
6
なります。
log(result); // 6
ネストの最高レベルを持つ
mul2
関数は、
multiply
および
mul1
によって形成されたクロージャーへのスコープにアクセスでき
mul1
。 そのため、
mul2
関数
mul2
、既にいくつかの値を返し、ガベージコレクターによって処理された実行が既に完了している関数で宣言された変数を使用して計算を実行できます。
上記では、抽象的な例を調べましたが、本質的には、長方形の箱の体積を計算するように設計された同じ関数です。
function volume(l,w,h) { return l * w * h; } const vol = volume(100,20,90) // 180000
カリー化されたバージョンは次のようになります。
function volume(l) { return (w) => { return (h) => { return l * w * h } } } const vol = volume(100)(20)(90) // 180000
そのため、カリー化は次の考えに基づいています。特定の関数に基づいて、特殊な関数を返す別の関数が作成されます。
関数のカリー化と部分的な使用
さて、おそらく、ネストされた関数のセットとして関数を表すとき、ネストされた関数の数は、関数の引数の数に依存するという感覚があります。 そして、カレーに関してはそうです。
既に見たボリュームを計算するための関数の特別なバージョンは、次のように作成できます。
function volume(l) { return (w, h) => { return l * w * h } }
ここでは、上記で説明したものと非常に類似したアイデアが適用されます。 この関数は次のように使用できます。
const hV = volume(70); hV(203,142); hV(220,122); hV(120,123);
そして、あなたはこれを行うことができます:
volume(70)(90,30); volume(70)(390,320); volume(70)(940,340);
実際、ここでは、
volume(70)
コマンドを使用して、ボディの体積を計算するための特殊な関数を作成した方法を見ることができます。
volume
関数は3つの引数を必要とし、2つのネストされた関数を含みます。以前のバージョンの同様の関数とは異なり、カリー化されたバージョンには3つのネストされた関数が含まれていました。
volume(70)
呼び出し後に発生した関数は、部分関数アプリケーションの概念を実装しています。 関数のカリー化と部分的な適用は互いに非常に似ていますが、概念は異なります。
部分適用では、関数は引数が少ない(アリティが小さい)別の関数に変換されます。 このような関数のいくつかの引数は固定されています(デフォルト値が設定されています)。
たとえば、次のような関数があります。
function acidityRatio(x, y, z) { return performOp(x,y,z) }
これに変換できます:
function acidityRatio(x) { return (y,z) => { return performOp(x,y,z) } }
performOp()
関数の実装は、検討中の概念に影響しないため、ここでは示しません。
値を修正する必要がある引数を指定して新しい
acidityRatio()
関数を呼び出すことで取得できる関数は、元の関数であり、引数の1つが修正され、この関数自体は元の引数より1つ少ない引数を取ります。
関数のカリー化されたバージョンは次のようになります。
function acidityRatio(x) { return (y) = > { return (z) = > { return performOp(x,y,z) } } }
ご覧のとおり、カリー化すると、ネストされた関数の数は元の関数の引数の数に等しくなります。 これらの各関数は、独自の引数を必要とします。 引数の関数が引数を受け入れない場合、または引数を1つだけ受け入れる場合、カリー化できないことは明らかです。
関数に2つの引数がある状況では、カリー化と部分適用の結果は一致すると言うことができます。 たとえば、次のような関数があります。
function div(x,y) { return x/y; }
最初の引数を修正して、2番目の引数のみを渡すときに計算を実行する関数を取得できるように書き換える必要があるとします。つまり、この関数を部分的に適用する必要があります。 次のようになります。
function div(x) { return (y) => { return x/y; } }
カレーの結果はまったく同じに見えます。
カリー化の概念の実用化と機能の部分適用について
関数のカリー化と部分的な適用は、さまざまな状況で役立ちます。 たとえば、再利用に適した小さなモジュールを開発する場合。
関数を部分的に使用すると、ユニバーサルモジュールを簡単に使用できます。 たとえば、オンラインストアがあり、そのコードには、割引を考慮して支払われる金額を計算するために使用される関数があります。
function discount(price, discount) { return price * discount }
顧客には特定のカテゴリがあります。「愛する顧客」と呼びましょう。10%の割引があります。 たとえば、そのようなクライアントが500ドルで何かを購入した場合、50ドルの割引を提供します。
const price = discount(500,0.10); // $50 // $500 - $50 = $450
このアプローチでは、この関数を常に2つの引数で呼び出す必要があることに気付くのは簡単です。
const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270
元の関数は、購入時に転送するだけでよい、事前に決められた割引レベルで新しい関数を受け取ることができる形式に縮小できます。 この例の
discount()
関数には2つの引数があります。 これを私たちが変換しようとしているものは次のとおりです。
function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1);
tenPercentDiscount()
関数は、
tenPercentDiscount()
関数を部分的に適用した結果です。 この関数の
tenPercentDiscount()
呼び出す場合、価格を渡すだけで十分であり、10%の
discount
、つまり
discount
引数が既に設定されています。
tenPercentDiscount(500); // $50 // $500 - $50 = $450
ストアに20%の割引を適用することを決めた顧客がいる場合、次のように適切な機能を使用して顧客と連携できます。
const twentyPercentDiscount = discount(0.2);
これで、
twentyPercentDiscount()
関数を呼び出して、20%の割引を考慮して、商品のコストを計算できます。
twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000
他の機能を部分的に適用するための汎用機能
任意の関数を受け入れ、そのバリアントを返す関数を開発します。これは、引数の一部がすでに設定されている関数です。 これを行うことができるコードは次のとおりです(同様の機能を開発する目標を設定した場合、結果として何か他のものを取得する可能性が非常に高くなります)。
function partial(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } }
partial()
関数は、部分的に適用される関数に変換する
fn
関数と、可変数のパラメーター
(...args
)を受け入れます。
rest
ステートメントは、
fn
に続くすべてのパラメーターを
args
に入れるために使用されます。
この関数は、可変数のパラメーター(
_arg
)も受け入れる別の関数を返します。 この関数は、元の
fn
関数を呼び出し、パラメーター
...args
および
..._arg
を
..._arg
ます(
spread
演算子を使用)。 関数は計算を実行し、結果を返します。
この関数を使用して、辺の1つが固定されている直方体の体積を計算するように設計された、既に馴染みのある
volume
関数のバリアントを作成します。
function volume(l,h,w) { return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000
ここでは、他の機能をカリー化するための汎用機能の例を見つけることができます。
まとめ
この記事では、関数のカリー化と部分的な適用について説明しました。 関数を変換するためのこれらのメソッドは、クロージャーと、JSの関数が最初のクラスのオブジェクトであるという事実のためにJavaScriptで実装されています(他の関数に引数として渡され、それらから返され、変数に割り当てられます)。
親愛なる読者! プロジェクトでカリー化技術と関数の部分適用を使用していますか?