関数型プログラミングの基本知識#
なぜ関数型プログラミングを学ぶのか?#
過去には、命令型プログラミングが主流でした。命令型プログラミングは最小の粒度のプログラミングであり、各要素の状態を正確に制御できます。
しかし、粒度が小さいということは自由度が高いことを意味する一方で、複雑でもあります。粒度が高いため、簡単でよく使われる機能を実現しようとすると、最初から構築しなければなりません。多くの基盤が再利用できません。
再利用可能な関数もいくつかあります。例えば、一般的な大小比較、ソート、フィルタリングなどです。これらの関数は再利用可能ですが、命令型プログラミングでは、これらの関数は付属品であり、コアではありません。
関数型プログラミングは宣言型のプログラミングパラダイムであり、そのコアは関数です。関数型プログラミングのコアは関数であり、状態ではありません。
では、関数型プログラミングは本質的に何を学ぶのでしょうか?思想で言えば、それは階層化です。コア関数を一つ一つの付属関数に分割するプロセスです。実装の観点から言えば、それは付属関数の API 構文です。
カリー化とは何か?#
関数型プログラミングにおいて欠かせない操作が二つあるとすれば、それはカリー化(Currying)と関数合成(Compose)です。カリー化は実際にはパイプライン上の加工ステーションであり、関数合成は私たちのパイプラインであり、複数の加工ステーションで構成されています。
f(a,b,c) → f(a)(b)(c)
カリー版の add 関数を書いてみましょう。
var add = function(x) {
return function(y) {
return x + y;
};
};
const increment = add(1);
increment(10); // 11
なぜこの単位関数が重要なのでしょうか?以前に言ったように、関数の戻り値は一つだけですか?
パイプラインをスムーズに組み立てるためには、各加工ステーションの出力が次の作業ステーションの入力にちょうど流れることを保証しなければなりません。したがって、パイプライン上の加工ステーションはすべて単位関数でなければなりません。
カリー化が関数合成と組み合わせると効果的である理由が理解できるでしょう。なぜなら、カリー化の処理結果はちょうど単一入力だからです。
関数型プログラミングの関連構文#
JavaScript における関数型プログラミングは、さまざまな便利な関数をサポートしており、主に以下のような種類があります:
-
マッピング(Map):配列の各要素に関数を適用し、新しい配列を作成します。この配列には関数適用後の結果が含まれます。
const numbers = [1, 2, 3, 4]; const doubled = numbers.map(x => x * 2); // [2, 4, 6, 8]
-
フィルタリング(Filter):テスト関数に基づいて配列の要素を保持するかどうかを決定し、テストを通過した要素を含む新しい配列を作成します。
const numbers = [1, 2, 3, 4]; const evens = numbers.filter(x => x % 2 === 0); // [2, 4]
-
還元(Reduce):配列の要素を組み合わせ、reducer 関数を通じて単一の値に減少させます。
Array.prototype.reduce()
メソッドは JavaScript において非常に強力なツールであり、配列内のすべての要素を単一の値にまとめる(または「減少させる」)ために使用されます。このメソッドは、配列内の値を累積したり、配列の内容を結合したり、単一の反復で複雑なデータ変換を実現するのに非常に便利です。reduce
メソッドの基本的な使い方は次のとおりです:array.reduce(callback(accumulator, currentValue, currentIndex, array), initialValue)
これはコールバック関数のデフォルトパラメータであり、callback:配列内の各値に対して実行される関数で、4 つのパラメータを含みます:
accumulator:累積器コールバックの戻り値を累積します;これは前回コールバックを呼び出したときに返された累積値、または提供された初期値(initialValue)です。
currentValue:現在処理中の配列の要素。
currentIndex(オプション):現在処理中の配列の要素のインデックス。initialValue が提供された場合、開始インデックスは 0、そうでない場合は 1 です。
array(オプション):reduce メソッドを呼び出した配列。initialValue(オプション):コールバック関数が最初に呼び出されたときの最初のパラメータ(accumulator)の値。初期値が提供されていない場合、配列の最初の要素が使用されます。
const numbers = [1, 2, 3, 4]; const sum = numbers.reduce((accumulator, current) => accumulator + current, 0); // 10
-
すべて(Every):配列内のすべての要素が提供されたテスト関数を満たすかどうかをチェックします。
いずれにせよ、every が返すのはブール値であり、ブール値でない場合はブール値に変換されて計算されます。
JavaScript において、Array.prototype.every()
メソッドは、配列内の各要素に対してテストを実行する関数を期待しています。コールバック関数が各要素に対して真値(truthy value)を返す場合、every
メソッドはtrue
を返します。そうでない場合はfalse
を返します。
このコールバック関数は必ずしもブール値(true
またはfalse
)を返す必要はありません。JavaScript の任意の値は真値または偽値(falsy value)として扱われます。コールバック関数が返す値がブールコンテキストでtrue
に変換できる場合、その値は真値と見なされます。一般的な偽値には0
、null
、undefined
、NaN
、空文字列""
およびfalse
自体が含まれます。それ以外のすべての値は真値と見なされます。
例えば、コールバック関数が数字1
を返す場合、1
はブール値ではありませんが、真値であるため、every
はそれをtrue
と解釈します。コールバック関数が0
(偽値)を返す場合、every
はそれをfalse
と解釈します。const numbers = [1, 2, 3, 4]; const allPositive = numbers.every(x => x > 0); // true
-
いずれか(Some):配列内に少なくとも 1 つの要素が提供されたテスト関数を満たすかどうかをチェックします。これは every と同様です。
const numbers = [1, 2, 3, 4]; const hasNegative = numbers.some(x => x < 0); // false
-
検索(Find):提供されたテスト関数を満たす配列内の最初の要素の値を返します。
const numbers = [1, 2, 3, 4]; const firstEven = numbers.find(x => x % 2 === 0); // 2
-
インデックス検索(FindIndex):提供されたテスト関数を満たす配列内の最初の要素のインデックスを返します。
const numbers = [1, 2, 3, 4]; const indexOFEven = numbers.findIndex(x => x % 2 === 0); // 1
-
ソート(Sort):配列の要素をソートし、ソートされた配列を返します。
const numbers = [4, 2, 3, 1]; numbers.sort((a, b) => a - b); // [1, 2, 3, 4]
-
スライス(Slice):配列の一部を返し、元の配列を変更しません。新しい配列を返し、start(その要素を含む)から end(その要素を含まない)までの arrayObject 内の要素を含みます。
const numbers = [1, 2, 3, 4]; const middleTwo = numbers.slice(1, 3); // [2, 3]
これらの関数は、JavaScript における関数型プログラミングの基本的なツールであり、宣言型で不変のプログラミングパラダイムを提供し、より明確でメンテナンスしやすいコードを書くのに役立ちます。
さあ、これらの機能を一度暗記してみましょう。
あなたが知っている関数型 JS の API を簡単に暗記してみてください
- every、その役割は配列の各 API を実行し、配列のすべての要素のコールバックが返す値が true であれば true を返し、1 つでも true でないものがあれば false を返します。
- some、every と同様で、1 つでも true が返れば true、すべてが false であれば false を返します。
- map、その役割はマッピングであり、配列内の各数をコールバック関数の戻り値に変換し、最後に組み合わせて新しい配列を返します。
- filter、その役割はフィルタリングであり、コールバック関数を満たす数を保持し、コールバック関数が false を返す数を削除します。受け取るコールバック関数もブール型を受け取ります。
- find、その役割は配列内の各数を検索し、配列の数がコールバック関数を満たす場合、その数を返します。コールバックはブール型を受け取ります。
- findindex、find と同様ですが、返すのはその数のインデックスです。
- sort、ソート、配列の数をルールに従ってソートします。ルールに従ってソートするには、2 つの数を受け取ります。負の数を返すと a が前、正の数を返すと b が前、0 を返すと変わりません。最後にソートされた配列を返します。
- slice、スライス、2 つのパラメータを受け取り、コールバックは受け取りません。2 つのパラメータは開始要素と終了要素であり、[,) の配列を返します。つまり、終了要素を含まない配列です。
- reduce、全体の配列を正規化する関数であり、配列を 1 つの数に変えることができます。簡単な応用としては累積器を作ることができます。また、配列をリストに変換する操作もできます。2 つのパラメータを受け取り、1 つはコールバック関数、もう 1 つは初期値で、コールバック関数の最初の値は以前の戻り値、2 番目の値は現在の値です。
これらは実際には関数型プログラミングだけでなく、Array のいくつかのオブジェクトメソッドでもあります。それ以外にどんなオブジェクトメソッドがありますか?#
concat()
:2 つ以上の配列を結合し、新しい配列を返します。copyWithin()
:配列内で、一連の要素を指定された位置から別の指定された位置にコピーし、変更された配列を返します。entries()
:配列内の各インデックスのキー / 値のペアを含む新しい Array Iterator オブジェクトを返します。every()
:配列のすべての要素が指定された関数のテストを通過したかどうかをテストします。fill()
:固定値を使用して、開始インデックスから終了インデックスまでのすべての要素を配列に埋めます。filter()
:提供された関数によって実現されたテストを通過したすべての要素を含む新しい配列を作成します。find()
:提供されたテスト関数を満たす配列内の最初の要素の値を返します。findIndex()
:提供されたテスト関数を満たす配列内の最初の要素のインデックスを返します。forEach()
:配列の各要素に提供された関数を一度実行します。from()
:類似配列または反復可能なオブジェクトから新しい配列インスタンスを作成します。includes()
:配列が指定された値を含むかどうかを判断し、含まれていればtrue
を返し、そうでなければfalse
を返します。indexOf()
:配列内で指定された要素の最初のインデックスを返し、存在しない場合は - 1 を返します。isArray()
:渡された引数が配列であるかどうかを判断します。join()
:配列内のすべての要素を結合して文字列を返します。keys()
:配列内の各インデックスキーを含む Array Iterator オブジェクトを返します。lastIndexOf()
:配列内で指定された要素の最後のインデックスを返し、存在しない場合は -1 を返します。map()
:新しい配列を作成し、その結果は配列内の各要素が提供された関数を一度呼び出した後の戻り値です。pop()
:配列から最後の要素を削除し、その要素の値を返します。push()
:配列の末尾に 1 つ以上の要素を追加し、新しい長さを返します。reduce()
:累積器と配列内の各要素(左から右)に関数を適用し、単一の値に減少させます。reduceRight()
:関数を累積器として受け取り、配列の各値(右から左)を単一の値に減少させます。reverse()
:配列内の要素の位置を反転させ、最初の要素が最後の要素になり、最後の要素が最初の要素になります。shift()
:配列から最初の要素を削除し、その要素の値を返します。slice()
:開始と終了(終了を含まない)によって決定される浅いコピーの配列の一部を返し、新しい配列オブジェクトとして返します。some()
:配列内のいくつかの要素が提供された関数によって実現されたテストを通過したかどうかをテストします。sort()
:配列の要素をソートし、配列を返します。splice()
:既存の要素を削除したり、新しい要素を追加したりして配列の内容を変更します。toString()
:指定された配列とその要素を表す文字列を返します。unshift()
:配列の先頭に 1 つ以上の要素を追加し、新しい長さを返します。valueOf()
:配列オブジェクトの原始値を返します。
新しいメソッド:Array.of()
:可変数のパラメータを持つ新しい配列インスタンスを作成し、パラメータの数やタイプに関係なく作成します。Array.at()
:整数値を受け取り、そのインデックスにある要素を返し、正数と負数のインデックスを使用できます。Array.flat()
:指定された深さに基づいて配列を再帰的に探索し、すべての要素を探索した子配列の要素と結合して新しい配列を作成します。Array.flatMap()
:最初にマッピング関数を使用して各要素をマッピングし、次に結果を新しい配列に圧縮します。