1. 序章

この記事では、関数型プログラミングについて説明します。 その基礎と原則を定義してから、関数型プログラミングとOOPを比較します。 最後に、人気のある関数型プログラミング言語をいくつか紹介します。

2. プログラミングパラダイム

2つの基本的なプログラミングパラダイムの特徴を確認しましょう。OOP(オブジェクト指向プログラミング)が発展した必須と、関数型プログラミングの基盤である宣言型です。

2.1. 命令型プログラミング

命令型のアプローチでは、プログラムは、目標の結果に到達するまでその状態を変更する一連のステップです。 そのアプローチは密接に関連していますフォンノイマン型コンピュータアーキテクチャ 、マシンコードの命令がグローバル状態を変更します。 マシンの状態は、メモリの内容とプロセッサレジストリで構成されます。 ほとんどのコンピューターはこのモデルを使用します。

命令型プログラムも同様に機能します。 割り当てステートメントは、メモリに格納されているデータを操作するために使用されます。 命令は特定の順序で実行され、それらのデータを使用して目的の結果を計算します。 命令型言語では、変数、ループ、条件文などの要素を使用することがよくあります。

要約すると、命令型プログラミングの最も重要な特性は次のとおりです。

  • 可変状態
  • 命令の段階的な実行、および
  • 指示の順序は重要です

2.2. 宣言型プログラミング

宣言型アプローチは、それを達成するために必要な一連のステップではなく、目的の結果の最終条件に焦点を当てます。したがって、宣言型プログラムは、一連のステートメントではなく、一連のプロパティ宣言です。結果のオブジェクトは持っている必要があります。

宣言型ソフトウェアを特徴付ける属性は次のとおりです。

  • 最終結果は外部状態に依存しません
  • 実行間の内部状態の欠如
  • 決定論—同じ入力引数に対して、プログラムは常に同じ結果を生成します
  • 実行の順序は必ずしも重要ではありません—非同期にすることができます

3. 関数型プログラミングの原則

関数型プログラミングは、宣言型プログラミングのサブセットです。 また、それ自体が別個のプログラミングパラダイムを表しています。 関数型プログラミングのアプローチでは、開発者は関数を作成して適用することによってプログラムを作成します。

このセクションでは、関数型プログラミングのコア原則を定義します。

例として、Scalaプログラミング言語を使用します。 Scalaは不純な機能言語と見なされています。 これにより、OOPスタイルと機能スタイルの両方を使用できます。 それにもかかわらず、それは関数型プログラミングに興味のある人が考慮すべき高度で人気のある技術です。

3.1. 第一級関数

関数は、関数型プログラミング言語の基本要素です。 この場合、関数は一級市民であるとよく言われます。 ただし、次の条件が満たされた場合にのみ、関数はファーストクラスであると言えます。

  • 関数を引数として別の関数に渡すことができます
  • 変数に値を割り当てるのと同じように、変数に関数を割り当てることができます
  • 関数は結果として別の関数を返すことができます

Scalaのファーストクラス関数の簡単な例を見てみましょう。

val sum = (a:Int, b: Int) => {a + b}

sumという変数に2つの数値を合計する関数を割り当てています。

第一級関数のもう1つの例を見てみましょう。

def calculation(fun:(Int, Int) => Int) {
  println(fun(10,5))
}

Calculation 関数は、パラメーターとして渡された関数funの結果を出力します。fun パラメーターは、2つの数値を次のように受け取る関数です。パラメータを設定し、結果として1つの数値を返します。 したがって、加算、減算、乗算、除算などの演算を計算に渡すことができます。

3.2. 純粋関数

純粋関数は数学関数の反映であると言えます。 これには、参照透過性と副作用の欠如という2つの重要な特性があります。

最初のプロパティである参照透過性は、特定の入力に対して、関数が何度実行されても、常に同じ結果を生成することを意味します。 EUR通貨をUSDに変換する簡単な例を分析してみましょう。

def eurToUsd(eur: Double): Double = {
  eur * getUsdExchangeRate()
}

ここで、 getUsdExchangeRate()関数が外部APIから米ドル通貨の為替レートを取得するとします。 ご存知のように、為替レートは絶えず変化しています。

関数getUsdExchangeRate()は実行ごとに異なる値を返す可能性があるため、同じ eur 入力値を指定すると、eurToUsd関数から異なる結果を取得することもできます。 したがって、eurToUsd関数は純粋関数ではありません。 これを非常に簡単に純粋関数に変換できます。

def eurToUsdPure(eur: Double, exchangeRate: Double): Double = {
  eur * exchangeRate;
}

これで、参照透過性の要件が満たされました。 この関数は、同じパラメーターのペアに対して同じ結果を返します。 たとえば、値10.0および1.5を渡すと、常に15.0が返されます。

2番目のプロパティ副作用の欠如は、関数の唯一の目的が、渡された引数のみに基づいて結果を計算することであることを意味します。 関数は、それ以外の状態を変更してはなりません。 副作用の例を次に示します。

  • コンソールに出力を送信する
  • ネットワークを介してデータを送信する
  • 関数のスコープ外で変数を変更する
  • ディスクへのデータの書き込み

3.3. 高階関数

高階関数は、引数として別の関数を受け取るか、実行の結果として関数を返します。 高階関数は、多くのプログラミング言語で広く使用されています。 このような関数のよく知られた例は、 .map .filter .reduce 、および.forEachです。

計算メソッドのパラメーターとして別の関数を受け取る関数はすでに見てきました。 次に、結果として別の関数を返す関数を見てみましょう。

def hello(name: String) = () => {
  val helloName = () => "Hello, " + name
  helloName()
}

の範囲内こんにちは関数、と呼ばれる関数を定義します helloName 、関数から返しますこんにちは結果として

3.4. 不変性

作成後に変更できない場合、データは不変です。 したがって、既存の値を変更することはできません。 新しい値を使用するには、完全に新しいオブジェクトを作成する必要があります。 ソースコードの観点から、不変のデータは通常次のとおりです。

  • 状態の変更を許可するメソッドを公開しません
  • 拡張できません
  • 可変フィールドを公開しません

その概念を具体的な例で分析してみましょう。 Scalaコレクションがどのように可変または不変になるかを見ていきます。

var mySet = scala.collection.mutable.Set[Int]()

mySet。という名前の可変セットを定義しました。その可変性のため、セットに値を追加できます。

mySet += 5

セットの参照を変更することもできます。

mySet = scala.collection.mutable.Set(9, 8, 7)

不変セットの例を見てみましょう。

val immutableSet = Set(1, 2)

そのセットにアイテムを追加したり、その参照を変更したりすることはできません。 試してみると、コンパイルエラーが発生します。 唯一の方法は、新しい値を持つセット全体を別のセットにコピーすることです。

val newSet = immutableSet + 3

不変性には、次のような多くの長所があります。

  • スレッドセーフ—デッドロックやデータ競合がなく、データを同期する必要がありません
  • 有効で一定の状態を保証します—データは初期化された状態でのみ存在できます
  • 不要または予測できない参照の変更を回避します

4. 関数型プログラミングと OOP

4.1. 比較

このセクションでは、OOPと関数型プログラミングのアプローチが互いにどのように異なるかを見ていきます。

機能的アプローチ: OOPアプローチ:
1. 実行の順序は必ずしも重要ではなく、非同期にすることもできます。 1. ステートメントの特定の実行シーケンスは非常に重要です。
2. 関数は基本的なプログラム構築要素です。 2. オブジェクトは、データモデリングの主な抽象化です。
3. これは、宣言型のパラダイムに従います。 3. これは、必須のパラダイムに従います。
4. 望ましい結果とその最終条件に焦点を当てています。 4. それは、望ましい結果をどのように達成できるかに焦点を当てています。
5. 不変性を保証します。 プログラムはステートレスである必要があります。 5. 状態の変化は、実行の重要な部分です。
6. 主な活動は、新しい関数を書くことです。 6. 主なアクティビティは、オブジェクトの構築、作成、および拡張です。

4.2. どちらの方がよいですか?

重要な質問は、どちらが優れているか、どちらを選択するかということです。

OOPとは、実際の単語オブジェクトを可能な限り反映したデータのモデリングです。 OOPのコア要素は、抽象化、カプセル化、継承、およびポリモーフィズムです。 関数型プログラミングは、プログラムのデータと動作を分離します。 関数型プログラミングの主な焦点は、特定のタスクを実行するための関数を書くことです。

関数型プログラミング言語は、通常、固定されたものがある場合にパフォーマンスが向上します。 プログラムが成長するにつれて、既存のものに新しい操作を追加することができます。 物事に対する一連のアクションが固定されている場合、OOP言語は良い選択です。 OOPアプローチでは、既存のメソッドを実装する新しいクラスを追加することでコードを拡張できます。

どちらのパラダイムにも多くの利点があります。 したがって、最新のプログラミング言語のほとんどでは、開発者は単一のコードベースで両方のアプローチを使用できます。 次のセクションでは、そのようなテクノロジーの例をいくつか紹介します。

5. 関数型プログラミング言語

5.1. Scala

Scala は、OOPと機能的アプローチを組み合わせた汎用プログラミング言語です。 強力な静的型システムを提供し、Java仮想マシンで実行されます。

5.2. Kotlin

Kotlin は、Java仮想マシン上で実行される汎用プログラミング言語でもあります。 さらに、KotlinはJavaと完全に相互運用できます。

5.3. Clojure

Clojure は、強力で純粋な関数型プログラミング言語です。 JVM、CLR(.NET)、およびWebブラウザーで実行できます。 したがって、これを使用して、フロントエンドアプリケーションとバックエンドアプリケーションの両方を開発できます。

6. 結論

この投稿では、関数型プログラミングのパラダイムについて説明しました。 そのコア原則と可能性を定義しました。 次に、それを一般的なOOPパラダイムと比較し、両方の目的を分析しました。 最後に、最新の関数型プログラミング言語の例をいくつか紹介しました。