関数型プログラミングは、状態やデータを変更せずに式や関数を使用してコンピュータープログラムを構築するパラダイムです。
これらの制限を尊重することにより、関数型プログラミングは、理解しやすく、バグに強いコードを書くことを目的としています。これは、コードの追跡を困難にするフロー制御ステートメント(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;
これも不純な関数であり、2つの理由から、(1)出力の計算に非ローカル変数を使用し、(2)変数を変更することで、外界に副作用を引き起こします。世界。
個人事業主vsscorpを通過する
このコードをデバッグする必要がある場合、これは面倒な場合があります。
id_count
の現在の値は何ですか? id_count
を変更している他の関数はどれですか? id_count
に依存する他の関数はありますか?
これらの理由により、関数型プログラミングでは純粋関数のみを使用します。
純粋関数のもう1つの利点は、並列化してメモ化できることです。前の2つの関数を見てください。それらを並列化またはメモ化することは不可能です。これは、パフォーマンスの高いコードの作成に役立ちます。
これまでのところ、関数型プログラミングはいくつかのルールに依存していることを学びました。以下のとおりです。
これらの条件を満たすと、コードは機能していると言えます。
JavaScriptには、関数型プログラミングを可能にするいくつかの関数がすでにあります。例: String.prototype.slice 、 Array.protoype.filter 、 Array.prototype.join 。
一方、 Array.prototype.forEach 、 Array.prototype.push 不純な機能です。
Array.prototype.forEach
と主張することができますは設計上不純な関数ではありませんが、考えてみてください。非ローカルデータを変更するか、副作用を実行する以外に、それを使用して何もすることはできません。したがって、それを不純な関数のカテゴリに入れてもかまいません。
また、JavaScriptには const 宣言。データを変更しないため、関数型プログラミングに最適です。
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()
という意味でデータを変更するため、データが不純になります。
[1, 2].concat([3, 4]) // [1, 2, 3, 4]
を使用して同じことを行うこともできます 展開する オペレーター。
[1, 2, ...[3, 4]]
Object.assign
提供されたオブジェクトから新しいオブジェクトに値をコピーします。関数型プログラミングは不変データを前提としているため、これを使用して既存のオブジェクトに基づいて新しいオブジェクトを作成します。
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
の出現で ES6 、これは、スプレッド演算子を使用して実行することもできます。
const newObj = {...obj};
純粋関数も作成できます。文字列を複製するために1つやってみましょうn
何度か。
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;
数学では、合成は、結合された出力を作成するために、ある関数の出力を別の関数の入力に渡すこととして定義されます。純粋関数を使用しているため、関数型プログラミングでも同じことが可能です。
例を示すために、いくつかの関数を作成しましょう。
最初の関数は範囲で、開始番号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は、これまでよりもはるかに優れた関数型プログラミング体験を可能にします。
関数型プログラミングは、宣言と式を使用してコンピュータープログラムを構築するパラダイムです。
ES6の新しい開発のおかげで、JavaScriptは、さまざまなファーストクラスの機能を提供するため、機能的であると同時にオブジェクト指向プログラミング言語であると言えます。
関数型プログラミングは、コード内のフロー制御を簡素化し、変数や状態の変化という形での予期せぬ事態を回避します。これはすべて、バグを回避し、コードを簡単に理解するのに役立ちます。
Lisp、Erlang、Haskell、Closure、Pythonは他の関数型プログラミング言語です。これらにおいて、Haskellは、他のプログラミングパラダイムを許可しないという意味で、純粋に関数型プログラミング言語です。
ES6またはECMAScript6は、JavaScriptの新しいバージョンであり、矢印関数、定数、スプレッド演算子などの多くの新機能が含まれています。