関数型プログラミング入門:JavaScriptパラダイム
公開: 2022-03-11関数型プログラミングは、状態やデータを変更せずに式や関数を使用してコンピュータープログラムを構築するパラダイムです。
これらの制限を尊重することにより、関数型プログラミングは、理解しやすく、バグに強いコードを書くことを目的としています。 これは、コードの追跡を困難にするフロー制御ステートメント( for
、 while
、 break
、 continue
、 goto
)の使用を回避することで実現されます。 また、関数型プログラミングでは、バグが発生しにくい純粋で決定論的な関数を作成する必要があります。
この記事では、JavaScriptを使用した関数型プログラミングの実行について説明します。 また、それを可能にするさまざまなJavaScriptメソッドと機能についても説明します。 最後に、関数型プログラミングに関連するさまざまな概念を調査し、それらが非常に強力である理由を確認します。
ただし、関数型プログラミングに入る前に、純粋関数と不純関数の違いを理解する必要があります。
純粋関数と不純関数
純粋関数はいくつかの入力を受け取り、固定出力を提供します。 また、それらは外界に副作用を引き起こしません。
const add = (a, b) => a + b;
ここで、 add
は純粋関数です。 これは、 a
とb
の値が固定されている場合、出力は常に同じになるためです。
const SECRET = 42; const getId = (a) => SECRET * a;
getId
は純粋関数ではありません。 その理由は、出力の計算にグローバル変数SECRET
を使用するためです。 SECRET
が変更された場合、 getId
関数は同じ入力に対して異なる値を返します。 したがって、それは純粋関数ではありません。
let id_count = 0; const getId = () => ++id_count;
これも不純な関数であり、これもいくつかの理由で発生します。(1)出力の計算に非ローカル変数を使用し、(2)変数を変更することで、外界に副作用を引き起こします。世界。
このコードをデバッグする必要がある場合、これは厄介なことがあります。
id_count
の現在の値は何ですか? id_count
を変更している他の関数はどれですか? id_count
に依存する他の関数はありますか?
これらの理由により、関数型プログラミングでは純粋関数のみを使用します。
純粋関数のもう1つの利点は、並列化してメモ化できることです。 前の2つの関数を見てください。 それらを並列化またはメモ化することは不可能です。 これは、パフォーマンスの高いコードの作成に役立ちます。
関数型プログラミングの信条
これまでのところ、関数型プログラミングはいくつかのルールに依存していることを学びました。 以下のとおりです。
- データを変更しないでください
- 純粋関数を使用する:固定入力用の固定出力、および副作用なし
- 式と宣言を使用する
これらの条件を満たすと、コードは機能していると言えます。
JavaScriptでの関数型プログラミング
JavaScriptには、関数型プログラミングを可能にするいくつかの関数がすでにあります。 例:String.prototype.slice、Array.protoype.filter、Array.prototype.join。
一方、Array.prototype.forEach、Array.prototype.pushは不純な関数です。
Array.prototype.forEach
は設計上不純な関数ではないと主張することができますが、それについて考えてみてください。非ローカルデータの変更や副作用を除いて、それを使って何もすることはできません。 したがって、それを不純な関数のカテゴリに入れてもかまいません。
また、JavaScriptにはconst宣言があります。これは、データを変更しないため、関数型プログラミングに最適です。
JavaScriptの純粋関数
JavaScriptによって与えられる純粋関数(メソッド)のいくつかを見てみましょう。
フィルター
名前が示すように、これは配列をフィルタリングします。
array.filter(condition);
ここでの条件は、配列の各項目を取得する関数であり、項目を保持するかどうかを決定し、そのための真のブール値を返す必要があります。
const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]
filterEven
は純粋関数であることに注意してください。 それが不純だったとしたら、フィルター呼び出し全体が不純になっていたでしょう。
地図
map
は、配列の各項目を関数にマップし、関数呼び出しの戻り値に基づいて新しい配列を作成します。
array.map(mapper)
mapper
は、配列の項目を入力として受け取り、出力を返す関数です。
const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]
減らす
reduce
は、配列を単一の値に減らします。
array.reduce(reducer);
reducer
は、累積値と配列内の次の項目を取得して、新しい値を返す関数です。 配列内のすべての値に対して、このように次々に呼び出されます。
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6
コンキャット
concat
は、既存の配列に新しいアイテムを追加して、新しい配列を作成します。 push()がデータを変更するという意味で、 push()
push()
とは異なり、データが不純になります。
[1, 2].concat([3, 4]) // [1, 2, 3, 4]
スプレッド演算子を使用して同じことを行うこともできます。

[1, 2, ...[3, 4]]
Object.assign
Object.assign
は、提供されたオブジェクトから新しいオブジェクトに値をコピーします。 関数型プログラミングは不変データを前提としているため、これを使用して既存のオブジェクトに基づいて新しいオブジェクトを作成します。
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
ES6の登場により、これはスプレッド演算子を使用して実行することもできます。
const newObj = {...obj};
独自の純粋関数の作成
純粋関数も作成できます。 文字列をn
回複製するために1つ実行してみましょう。
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
この関数は文字列をn
回複製し、新しい文字列を返します。
duplicate('hooray!', 3) // hooray!hooray!hooray!
高階関数
高階関数は、関数を引数として受け取り、関数を返す関数です。 多くの場合、これらは関数の機能を追加するために使用されます。
const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };
上記の例では、関数を受け取り、ラップされた関数が実行される前にメッセージをログに記録する関数を返すwithLog
高階関数を作成します。
const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7
withLog
HOFは他の関数でも使用でき、競合や余分なコードの記述なしで機能します。 これがHOFの美しさです。
const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!
結合関数を定義せずに呼び出すこともできます。
withLog(hype)('Sale'); // calling hype // Sale!!!
カリー化
カリー化とは、複数の引数を取る関数を1つまたは複数のレベルの高階関数に分解することを意味します。
add
関数を見てみましょう。
const add = (a, b) => a + b;
カレーをするときは、次のように引数を複数のレベルに分けて書き直します。
const add = a => { return b => { return a + b; }; }; add(3)(4); // 7
カリー化の利点はメモ化です。 関数呼び出しで特定の引数をメモ化できるようになったため、複製や再計算を行わずに後で再利用できます。
// assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);
これは、どこでも両方の引数を使用するよりも確かに優れています。
// (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());
カリー化された関数を再フォーマットして簡潔に見せることもできます。 これは、カリー化関数呼び出しの各レベルが1行のreturnステートメントであるためです。 したがって、ES6の矢印関数を使用して、次のようにリファクタリングできます。
const add = a => b => a + b;
構成
数学では、合成は、結合された出力を作成するために、ある関数の出力を別の関数の入力に渡すこととして定義されます。 純粋関数を使用しているため、関数型プログラミングでも同じことが可能です。
例を示すために、いくつかの関数を作成してみましょう。
最初の関数はrangeです。これは、開始番号a
と終了番号b
を取り、 a
からb
までの番号で構成される配列を作成します。
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
次に、配列を受け取り、その中のすべての数値を乗算する関数multiplyがあります。
const multiply = arr => arr.reduce((p, a) => p * a);
これらの関数を一緒に使用して階乗を計算します。
const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720
階乗を計算するための上記の関数は、 f(x) = g(h(x))
に似ているため、合成プロパティを示します。
結びの言葉
純粋関数と不純関数、関数型プログラミング、それを支援する新しいJavaScript機能、および関数型プログラミングのいくつかの重要な概念について説明しました。
この作品が関数型プログラミングへの興味をそそり、コードでそれを試す動機となることを願っています。 私たちは、それがあなたのソフトウェア開発の旅における学習経験とマイルストーンになることを確信しています。
関数型プログラミングは、コンピュータープログラムを作成するための、十分に研究された堅牢なパラダイムです。 ES6の導入により、JavaScriptは、これまでになく優れた関数型プログラミング体験を可能にします。