純粋関数は副作用を発生させず、同じ入力パラメータで呼び出された場合に常に同じ出力を返す関数です。
純粋関数は、コードをクリーンで保守しやすく、テストしやすいものにするために使用できます。これらの関数は予測可能で外部の状態を変更しないため、これらのタスクに最適です。
また、デバッグも容易なため、複雑なソフトウェアシステムの開発に役立ちます。ここでは、JavaScriptにおける純粋関数とその特性、利点について説明します。
純粋関数の特性
関数を「純粋」にするには、いくつかの要件を満たす必要があります。
一定の戻り値
純粋関数は、呼び出される回数に関係なく、同じ入力が与えられた場合は常に同じ値を返さなければなりません。
たとえば、以下の関数を考えてみましょう。
function multiply(a, b) { return a * b; }
上記の例にあるmultiply関数は、常に2つの引数の積を返します。同じ引数が与えられた場合、一定の戻り値を持ちます。
この関数を同じ引数で数回呼び出すと、毎回同じ出力が得られます。たとえば:
multiply(2, 3); // 6を返す multiply(2, 3); // 6を返す multiply(2, 3); // 6を返す
一方、以下の例を考えてみましょう。
function multiplyRandomNumber(num) { return num * Math.floor(Math.random() * 10); } multiplyRandomNumber(5); // 予測不可能な結果 multiplyRandomNumber(5); // 予測不可能な結果 multiplyRandomNumber(5); // 予測不可能な結果
上記のmultiplyRandomNumber関数は、呼び出すたびに異なる結果を返すため、不純になります。この関数の結果は予測不可能なため、それに依存するコンポーネントをテストするのは困難です。
副作用がない
純粋関数は副作用を発生させてはいけません。副作用とは、グローバル変数の変更、コンソール出力、ネットワークリクエスト、DOM操作など、関数のスコープ外の状態や動作の変更を指します。
純粋関数が副作用を持つ場合、外部の状態に影響を与え、副作用のないという原則に違反するため、もはや純粋ではなくなります。したがって、純粋関数は副作用を避けてプログラムの状態を変更しないようにします。
たとえば、以下の例を考えてみましょう。
let count = 0; function increment() { count++; console.log(count); } increment(); // 1をログ出力 increment(); // 2をログ出力 increment(); // 3をログ出力
この例にあるincrement関数は、スコープ外のcount変数を変更するという副作用があります。また、コンソールにもログを出力しています。
この関数は副作用があるため純粋ではありません。そのため、出力を予測したり、単独でテストしたりすることが難しくなります。これを純粋にするには、count変数を引数として取り、外部の状態を変更せずに増加した値を返すように変更することができます。
次のようにします。
function increment(count) { return count + 1; } increment(1); // 2を返す increment(1); // 2を返す increment(1); // 2を返す
上記の例にあるincrement関数のバージョンには、外部変数を変更したり、値をログ出力したりする副作用はありません。また、呼び出す回数に関係なく、同じ入力に対して同じ値を返します。したがって、純粋関数です。
その他の特性
一定の戻り値を持ち、副作用を発生させないことに加えて、純粋なJavaScript関数を作成する際には、以下のルールを守る必要があります。
- 関数は引数を変更してはいけません。代わりに、操作に突然変異が必要な場合は、引数のコピーを作成してコピーを突然変異させます。
- 関数は常に戻り値を持たなければなりません。関数が戻り値や副作用を持たない場合、何もしません。
- 関数は外部の状態に依存してはいけません。
純粋関数の利点
純粋関数は不純関数に対して特定の利点を持っており、その中には以下のようなものがあります。
テストの容易性
純粋関数は、入出力の動作が明確に定義されているため、テストが容易です。また、純粋関数は外部の状態や副作用に依存しません。したがって、プログラムの他の部分との依存関係や相互作用を心配することなく、単独でテストすることができます。
対照的に、外部の状態に依存したり、副作用を発生させたりする不純関数をテストすることは、関数の動作がプログラムの状態やその他の外部要因に依存する可能性があるため、より困難になる場合があります。これにより、包括的なテストケースを作成し、関数がすべてのシナリオで正しく動作することを保証することが難しくなります。
メモ化
純粋関数は、常に同じ入力に対して同じ出力を生成し、副作用を発生させないため、簡単にメモ化することができます。
これらのプロパティを利用してメモ化を使用することにより、特定の入力に対する純粋な関数呼び出しの結果をキャッシュすることができます。そうすると、関数は同じ入力で次に呼び出されたときにキャッシュされた結果を返すことができます。
純粋関数をメモ化することで、プログラムのパフォーマンスを向上させることができます。特に、プログラムの状態への干渉を心配することなく、同じ入力を繰り返し扱う高価な計算の場合に有効です。
対照的に、不純関数は、プログラムの状態や外部要因に応じて同じ入力に対して異なる結果を生成する可能性があります。これにより、関数の依存関係や外部の状態が呼び出し間で変化した場合、キャッシュされた結果が有効ではなくなる可能性があるため、メモ化が困難になります。
並行性
純粋関数は外部の状態を変更したり副作用を発生させたりしないため、スレッドセーフです。競合状態や同期の問題を心配することなく、同時に実行することができます。
対照的に、不純関数は相互に干渉したり、並列に実行されたときに予期しない動作を発生させたりする可能性があるため、同時に実行するのは困難です。たとえば、2つのスレッドが同じグローバル変数にアクセスして変更した場合、お互いの変更を上書きしたり、矛盾した結果を生成したりする可能性があります。
純粋関数と不純関数
各タイプに用途があるため、純粋関数と不純関数の組み合わせを使用してプログラムを作成することができます。
純粋関数は最適化、テスト、並列化が容易なため、関数型プログラミング、キャッシュ、テスト、並列プログラミング、データ処理タスクなどのユースケースに適しています。
しかし、不純関数はテストや並行性において課題を提示しますが、可変データ構造を扱う場合や外部システムやリソースと対話する場合に役立ちます。
コメントする