JavaScript 関数型プログラミングの基礎

JavaScript 関数型プログラミングの基礎

1. はじめに

関数型プログラミングは長い歴史がありますが、近年になって世間の注目を集めるようになりました。関数型プログラミングをサポートしていない多くの言語も、クロージャや匿名関数といった非常に典型的な関数型プログラミングの機能を積極的に追加しています。多数のフロントエンド フレームワークも、関数型プログラミングの機能を使用していることを自慢しており、関数型プログラミングに関連すると非常に高度なものであるかのように感じられます。さらに、RxJS、cycleJS、ramdaJS、lodashJS、underscoreJS など、関数型プログラミング専用のフレームワークやライブラリもいくつかあります。関数型プログラミングはますます人気が高まっています。このプログラミングパラダイムを習得することは、高品質で保守可能なコードを書くために有益なので、習得する必要があります。

2. 関数型プログラミングとは何ですか?

Wikipedia の定義:関数型プログラミング (英語: functional programming) は、関数型プログラミングとも呼ばれ、コンピューター操作を数学的な関数の計算と見なし、プログラム状態や可変オブジェクトの使用を避けるプログラミング パラダイムです。

3. 純粋関数(関数型プログラミングの基礎、副作用のない関数)

中学校数学では、関数 f の定義は、入力 x に対して、一意の出力 y=f(x) を生成するというものです。これは純粋な関数です。次の 2 つの条件を満たします。1. この関数は、入力値が同じ場合、常に同じ出力を生成します。関数の出力は、現在の実行環境のコンテキストとは独立しています。 2. この関数の実行プロセスは実行環境に影響を与えません。つまり、副作用はありません (イベントのトリガー、http リクエストの開始、印刷/ログ記録など)。簡単に言えば、関数の出力が外部環境の影響を受けず、外部環境に影響を与えない場合、その関数は純粋関数、つまり論理演算と数学演算のみに焦点を当てており、同じ入力では常に同じ出力が得られます。 JavaScript の組み込み関数には、多くの純粋関数と多くの不純関数が含まれています。

純粋関数: Array.prototype.sliceArray.prototype.mapString.prototype.toUpperCase

不純な関数: Math.randomDate.nowArray.propertytype.splice

ここでは、スライス メソッドとスプライス メソッドを例に挙げます。

var xs = [1,2,3,4,5];
// 純粋なxs.slice(0,3);
//=> [1,2,3]
xs.スライス(0,3);
//=> [1,2,3]
xs.スライス(0,3);
//=> [1,2,3]

// 不純な xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []


配列の slice メソッドを呼び出すと毎回まったく同じ結果が返され、 xs は変化しませんが、 splice メソッドを呼び出すと毎回異なる値が返され、 xs が認識できなくなることがわかります。純粋関数の使用を強調するのは、純粋関数がキャッシュ可能性、移植性、テスト可能性、並列コンピューティングの点で不純な関数に比べて大きな利点を持っているためです。ここではキャッシュ可能性を例に挙げます。

var squareNumber = memoize(function(x){ return x*x; });
平方数(4);
//=> 16
squareNumber(4); // キャッシュから入力値4の結果を読み取ります //=> 16


では、不純な関数を純粋にするにはどうすればよいでしょうか?たとえば、次の関数:

var 最小値 = 21;
var checkAge = function(age) {
  年齢 >= 最小値を返します。
};


この関数の戻り値は、システムの状態に依存する可変変数 minimum の値によって決まります。大規模なシステムでは、外部状態への依存がシステムの複雑さの主な原因となります。

var checkAge = function(age) {
  var 最小値 = 21;
  年齢 >= 最小値を返します。
};


変換により、checkAge はシステムの状態に依存しない純粋な関数になりました。ただし、最小値はハードコードされた方法で定義されているため、関数のスケーラビリティが制限されます。次のカリー化で、関数メソッドを使用してこの問題をエレガントに解決する方法を確認できます。したがって、関数を純粋にする基本的な方法は、システム状態に依存しないことです。

4. 関数カリー化

カリー化の概念は単純です。低階関数を高階関数に変換するプロセスをカリー化と呼びます。

鮮明な比喩を使うと:

たとえば、加算演算 var add = (x, y) => x + y の場合、次のようにカリー化できます。

//es5 の記述 var add = function(x) {
  関数(y)を返す{
    x + y を返します。
  };
};

//es6 の書き込み var add = x => (y => x + y);

//試してみる var increment = add(1);
var addTen = add(10);

インクリメント(2); // 3

10を追加します(2); // 12

加算のような非常に単純な関数の場合、カリー化は役に立ちません。上記の checkAge 関数を覚えていますか?次のようにカレー化できます:

var checkage = min => (age => age > min);
var checkage18 = checkage(18);
チェック18(20);
// =>真


これは、関数のカリー化が関数を「プリロード」し、1 つまたは 2 つの引数を渡し、それらの引数を記憶する新しい関数を取得する機能であることを示しています。ある意味では、これは一種のパラメータのキャッシュであり、関数を記述する非常に効率的な方法です。

var curry = require('lodash').curry;

// 2 つの純粋関数をカリー化 var match = curry((what, str) => str.match(what));
var filter = curry((f, ary) => ary.filter(f));

// 文字列にスペースが含まれているかどうかを確認します var hasSpaces = match(/\s+/g);

hasSpaces("hello world"); // [ ' ' ]
hasSpaces("スペースなし"); // null

var findSpaces = filter(hasSpaces);

findSpaces(["tori_spelling", "tori amos"]); // ["tori amos"]

5. 関数合成

次に示すように、文字列に対していくつかの列操作を行う必要があるとします。例として、文字列に対して 2 つの操作のみを実行します。新しい関数 shut を定義し、最初に toUpperCase を呼び出し、戻り値を exclaim 関数に渡します。これのどこが間違っているのでしょうか。エレガントではありません。やることがたくさんあれば、ネストされた関数は非常に深くなり、コードは内側から外側に実行されますが、これは直感的ではありません。コードは右から左に実行されることを期待します。このとき、組み合わせを使用する必要があります。

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };

var 叫び = 関数(x){
  戻り値 exclaim(toUpperCase(x));
};

叫ぶ("ピエロを送り込め");
//=> 「ピエロを送り込め!」

コンポジションを使用すると、次のようにシャウト関数を定義できます。

// 作成を定義する
var compose = (...args) => x => args.reduceRight((value, item) => item(value), x);

var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };

var 叫ぶ = compose(exclaim, toUpperCase);

叫ぶ("ピエロを送り込め");
//=> 「ピエロを送り込め!」

コードは右から左に実行されるため、非常に明確で理解しやすいです。私たちが定義した compose は、任意の数の純粋関数を組み合わせることができる N 面接着剤のようなものです。この柔軟な組み合わせにより、機能コードをビルディングブロックのように組み合わせることができます。

var head = function(x) { return x[0]; };
var 逆順に = 減らす(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head,reverse);

last(['ジャンプキック', 'ラウンドハウス', 'アッパーカット']);
//=> 'アッパーカット'

6. 宣言型コードと命令型コード

命令型コード: マシンに物事をどのように行うか (how) を指示します。これにより、ユーザーが何を望んでいるか (what) に関係なく、マシンはユーザーのコマンドに従ってそれを実行します。宣言型コード: 「マシン」に何が必要か (what) を伝え、マシンにそれをどのように実行するか (how) を判断させます。命令型とは異なり、宣言型では、ステップごとの指示ではなく式を記述します。 SQL を例に挙げてみましょう。SQL には「最初にこれを実行してから、あれを実行する」というコマンドはありません。代わりに、データベースから取得するデータを指定する式だけがあります。データの取得方法は、それ自体で決定されます。将来的にデータベースのアップグレードや SQL エンジンの最適化が行われる場合でも、クエリ ステートメントを変更する必要はまったくありません。これは、式を解析して同じ結果を得る方法が複数あるためです。ここで、理解しやすくするために、例を見てみましょう。

// 命令型 var makes = [];
(var i = 0; i < cars.length; i++) {
  makes.push(cars[i].make);
}

// 宣言型 var makes = cars.map(function(car){ return car.make; });

命令型ループでは、最初に配列をインスタンス化する必要があり、インタープリターはインスタンス化ステートメントを実行した後にのみ後続のコードの実行を続行します。次に、すべての部品が露出した状態で車を運転しているときと同じように、手動でカウンターを増やしながら、車のリストを反復処理します。これは優秀なプログラマーが行う行為ではありません。宣言的な記述は式であり、カウンターを反復処理する方法や返された配列を収集する方法の詳細は隠されています。どのように行うかではなく、何をするかを指定します。より明確で簡潔であることに加えて、マップ関数は独立してさらに最適化することができ、インタープリターに組み込まれた非常に高速なマップ関数を使用することもできるため、主要なビジネス コードを変更する必要がありません。関数型プログラミングの明らかな利点は、この宣言型コードです。副作用のない純粋な関数の場合、関数が内部的にどのように実装されるかを考慮する必要がなく、ビジネス コードの記述に集中できます。コードを最適化するときは、これらの安定した堅牢な機能にのみ焦点を当てる必要があります。逆に、不純な非機能コードは副作用を生成したり、外部のシステム環境に依存したりするので、使用時にはこれらの不純な副作用を常に考慮する必要があります。複雑なシステムでは、これはプログラマーの精神に非常に大きな負担をかける可能性があります。

7. ポイントフリー

ポイントフリー モードでは、データを教える必要がありません。つまり、関数では、どのような種類のデータに対して操作を行うのかを指定する必要はありません。ファーストクラス関数、カリー化、および合成は、このパターンを実現するのに大いに役立ちます。

// ポイントフリーではない。データが言及されているから: 単語
var snakeCase = 関数 (単語) {
  word.toLowerCase().replace(/\s+/ig, '_') を返します。
};

// ポイントフリー
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

このスタイルは、不要な命名を減らし、コードを簡潔かつ普遍的に保つのに役立ちます。もちろん、一部の関数で Point Free スタイルで記述するには、コードの他の場所では Point Free スタイルをあまり使用しない必要があり、ここで独自の選択を行う必要があります。

8. サンプルアプリケーション

上記の知識を基に、サンプル アプリケーションを作成します。ここでは、lodash や他のライブラリの代わりに ramda を使用します。 Ramda は、compose や curry などの多くの機能を提供します。私たちのアプリケーションは次の 4 つのことを行います。

1. 特定の検索キーワードに基づいてURLを構築する

2. flickrにAPIリクエストを送信する

3. 返されたJSONをHTML画像に変換する

4. 写真を画面に配置する 上記では、Flickr の API からデータを取得し、写真を画面に配置するという 2 つの不純なアクションについて説明しました。まず、これら 2 つのアクションを定義して、分離できるようにしましょう。ここでは、jQuery の getJSON 関数を単純にラップし、カリー関数に変換し、パラメータも交換します。これらを Impure 名前空間に配置して分離し、危険な関数であることを認識できるようにします。関数カリー化と関数合成のテクニックを使用して、関数型アプリケーションを作成できます。

プレビュー アドレス: https://code.h5jun.com/vixe/1/edit?html,js,output なんと素晴らしい宣言型仕様でしょう。何をするかだけを示しており、どのようにするかは示していません。これで、コードの各行を方程式として考えることができ、変数名によって表されるプロパティが方程式の意味になります。

IX. 結論

小さいながらも現実的なアプリケーションに新しいスキルを適用する方法を見てきましたが、例外処理とコード分岐についてはどうでしょうか?破壊的な関数を名前空間に配置するのではなく、アプリケーション全体を機能させるにはどうすればよいでしょうか?アプリケーションをより安全かつ表現力豊かにするにはどうすればよいでしょうか?次の記事では、Functor、Monad、Applicative などの概念など、関数型プログラミングのより高度な知識を紹介します。

上記は、JavaScript 関数型プログラミングの基礎の詳細な内容です。JavaScript 関数型プログラミングの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JavaScript の基礎: 即時実行関数
  • JavaScript 関数の基本
  • JavaScriptの基本機能の概要
  • JavaScript の時間関数の基本的な紹介
  • JavaScript 基本機能の詳しい説明

<<:  Linux で実行中のすべてのプロセスを表示する方法

>>:  MySQL の重複データの処理方法 (防止と削除)

推薦する

CSS フロート(float, clear)の人気の解説と体験談

私はかなり昔に CSS に触れましたが、フローティングについてはいつも混乱していました。私の理解が浅...

Reactの新バージョンのライフサイクルフック機能と使用方法の詳細な説明

旧ライフサイクルと比較して 3つのフックが廃止され、2つの新しいフックが追加されましたReact16...

JS ベースのページフローティングボックスを実装するためのサンプルコード

スクロール バーを下に引くと、主にposition:fixed;スタイルにより、フローティング ボッ...

Dockerコンテナ監視の原理とcAdvisorのインストールおよび使用方法

本番環境におけるコンテナの稼働状況を監視することは非常に重要です。監視を通じて、コンテナの稼働状況を...

Vue マルチ選択リスト コンポーネントの詳細な説明

マルチ選択は、すべてのオプションを一覧表示し、ユーザーが Ctrl/Shift キーを使用して複数選...

MySQL で 2 つのテーブルをクエリする場合の from と join の違いの概要

序文MySQL では、複数テーブル結合クエリは非常に一般的な要件です。複数テーブルクエリを使用する場...

W3C チュートリアル (13): W3C WSDL アクティビティ

Web サービスは、アプリケーション間の通信に関係します。 WSDL は、XML ベースの Web ...

20個のJavaScriptワンラインコードを共有する

目次1. ブラウザのクッキーの値を取得する2. RGBを16進数に変換する3. クリップボードにコピ...

ReactにおけるuseRefの具体的な使い方

React の経験がある人なら、コンポーネントインスタンスオブジェクトや DOM オブジェクトを取得...

Vue ボタンの権限制御の導入

目次1. 手順1. ボタンの権限を定義する2. ストアを定義する3. 権限指示を作成する4. パーミ...

Excel をインポートするときに js で時間を変換する正しい方法について

目次1. 基本2. 問題の説明3. 解決策付録: js を使用して Excel の日付形式を変換する...

ネイティブ JS を使用してタッチスライド監視イベントを実装する方法

序文今日はちょっとしたデモを書きました。左右にスワイプするロジックに関わる部分があります。当初はプラ...

Linux sftp コマンドの使用法の概要

sftp は、安全なファイル転送プロトコルである Secure File Transfer Prot...

ハイパーコネクションの4つの状態の適用の詳細な説明

ブラウザの問題かもしれないと思うかもしれませんが、スタイル定義の順序が間違っている可能性が高いです。...

IE8 互換性について: X-UA-compatible 属性の説明

問題の説明:コードをコピーコードは次のとおりです。 <meta http-equiv=&quo...