第2章 プログラム基礎

プログラミング・パラダイム

プログラミング・パラダイムとは、プログラミングやソフトウェア開発のスタイルや「哲学」を表現する一連の概念です。主なプログラミングパラダイムには以下のようなものがあります。

  • 手続き型プログラミング: これは最も古典的なパラダイムで、プログラムを一連の「手続き」または手順(関数または手続きと呼ばれる)として表現します。C言語はこのパラダイムの典型的な例です。
  • オブジェクト指向プログラミング(OOP): データと手続きを一緒に「オブジェクト」にカプセル化するという考え方に基づいています。これにより、コードの再利用、モジュール性、維持管理が容易になります。Java、Python、Rubyなど多くの現代の言語がこのパラダイムを採用しています。
  • 関数型プログラミング: 関数型プログラミングは数学の関数に基づくパラダイムで、データの不変性と副作用のない関数(つまり、関数がプログラムの状態を変更することなく、入力から出力を計算する)を重視します。これにより、バグを減らし、テストや並行処理を容易にすることができます。HaskellやErlangはこのパラダイムの典型的な例ですが、JavaScriptやPythonのような他の言語でも関数型スタイルを部分的に採用しています。
  • 宣言型プログラミング: このパラダイムでは、プログラムが何をするべきか(「何」)を記述し、それがどのように達成されるべきか(「どのように」)を記述しません。SQLは宣言型パラダイムの一例で、データベースからデータをどのように取得するかではなく、どのデータが必要かを記述します。
  • 並行/並列プログラミング: これは特定のパラダイムというよりは、一部のプログラミング言語が採用している概念で、複数の計算を同時に(または見かけ上同時に)実行するための技術です。これにより、パフォーマンスを向上させたり、リアルタイムシステムを実装したりすることが可能になります。GoやErlangは、このタイプのプログラミングを直接サポートする言語の例です。

これらのパラダイムは、異なる問題や状況に対応するために、または開発者の好みや考え方に合わせて、プログラムを構築するための異なる道具や方法を提供します。多くの現代の言語は、これらのパラダイムを複数組み合わせて使用する「マルチパラダイム」アプローチを採用しています。


手続き型プログラミング

手続き型プログラミングは、最も基本的なプログラミングパラダイムの一つで、一連の手続き(または操作)を順番に実行することで問題を解決します。

歴史

このパラダイムは、コンピュータの初期の日々から存在しています。1960年代に開発されたFORTRANとALGOLは、手続き型プログラミングの初期の例です。その後、1970年代に開発されたC言語は、このパラダイムを大いに進化させ、現代の多くの言語の設計に影響を与えました。

特徴

手続き型プログラミングでは、一連の命令(手続き)が順番に実行されます。これらの手続きは、関数またはサブルーチンとしてまとめることができ、特定のタスクを実行します。

メリット

手続き型プログラミングは直感的で理解しやすいです。それは、我々が日常的にタスクを処理する方法(すなわち、手順を順番に実行する)に非常に近いからです。また、このパラダイムは、プログラムの実行順序を明確に示しているため、予測可能性と再現性があります。

デメリット

一方、手続き型プログラミングは、大規模で複雑なプログラムを管理するのが難しいことがあります。特に、変数の状態を追跡するのが困難な場合や、コードの再利用が難しい場合があります。また、このパラダイムは、データと処理が密接に関連している現代のプログラミング課題(例えば、オブジェクト指向プログラミングが適している)には必ずしも適していません。

手続き型プログラミングの例

// プロシージャ(または関数)の定義
function add(x, y) {
    return x + y
}

// 主処理の開始
var a = 5
var b = 10

// プロシージャの呼び出し
var result = add(a, b)

// 結果の出力
print(result)  // 15


オブジェクト指向プログラミング

オブジェクト指向プログラミング (OOP) は、データとそれに対する操作を一緒に「オブジェクト」として捉え、その相互作用によってプログラムを構築するパラダイムです。

歴史

オブジェクト指向プログラミングのアイデアは、1960年代に開始されたプロジェクトである「Simula」に由来します。しかし、このパラダイムは1980年代にSmalltalkとともに主流になり、その後Java、C++、Python、Rubyなどの言語で広く採用されました。

特徴

オブジェクト指向プログラミングの主な特徴は以下の通りです。

  1. クラスとインスタンス: クラスはオブジェクトの設計図であり、インスタンスはその設計図に基づいて作成された実体です。
  2. 継承: あるクラスの特性を他のクラスが引き継ぐことで、コードの再利用を促進します。
  3. カプセル化: オブジェクトの内部データとメソッドを隠蔽し、外部から直接アクセスできないようにします。
  4. ポリモーフィズム: 同じインターフェイスで異なる動作を実装し、オブジェクトのタイプに応じて動的に動作を変更できます。

メリット

オブジェクト指向プログラミングは、大規模なソフトウェアプロジェクトを管理しやすくします。データと機能がオブジェクトにカプセル化されているため、コードはモジュール化され、再利用しやすくなります。

デメリット

一方、オブジェクト指向プログラミングの理論と概念は、初学者にとっては複雑で難しいものとなる可能性があります。また、継承の過度な使用は、コードの複雑性を増加させ、予期しない問題を引き起こす可能性があります。

オブジェクト指向プログラミングの例

// クラスの定義
class Dog {
    // インスタンス変数(プロパティ)
    name = ""
    
    // コンストラクタ
    function Dog(name) {
        this.name = name
    }

    // メソッド
    function bark() {
        print(this.name + " says: Woof!")
    }
}

// インスタンスの生成
var myDog = new Dog("Fido")

// メソッドの呼び出し
myDog.bark()  // "Fido says: Woof!"


関数型プログラミング

関数型プログラミングは、プログラムの実行を数学的な関数の評価と見なし、状態と可変データを避けるプログラミングパラダイムです。

歴史

関数型プログラミングの概念は、ラムダ計算という数学の理論から生まれました。最初の関数型プログラミング言語は、1950年代に開発されたLISPです。その後、Haskell、Scheme、Clojure、Erlang、F#など、多くの関数型プログラミング言語が登場しました。

特徴

関数型プログラミングは以下の特徴を持ちます。

  1. 純粋関数: 入力だけが出力に影響を与え、副作用(外部の状態の変更や入力以外のデータに依存する行為)を持たない関数を使用します。
  2. 不変性: データは作成後に変更されません。新しいデータが必要な場合は、新しいインスタンスが作成されます。
  3. 第一級関数: 関数は第一級市民であり、変数に割り当てたり、他の関数に渡したり、他の関数から返したりできます。
  4. 高階関数: 他の関数を引数として受け取り、または結果として返す関数を使用します。

メリット

関数型プログラミングは、バグを減らし、コードを理解しやすくするのに役立つ特性を持っています。また、純粋関数は予測可能でテストしやすいです。並行性と並列性を扱うのに有用なパラダイムでもあります。

デメリット

一方、関数型プログラミングの学習曲線は急であり、初学者には困難を伴う場合があります。また、パフォーマンスの観点からは、不変性と再帰に依存する特性が問題となることがあります。

関数型プログラミングの例

// 純粋関数の定義
function square(x) {
    return x * x
}

// 高階関数の定義
function map(func, list) {
    // 新しいリストを作成
    var newList = []
    for (var i = 0; i < list.length; i++) {
        newList.push(func(list[i]))
    }
    return newList
}

// 使用例
var numbers = [1, 2, 3, 4, 5]
var squared = map(square, numbers)

// 結果の出力
print(squared)  // [1, 4, 9, 16, 25]


宣言型プログラミング

宣言型プログラミングは、プログラムの「何を」行うべきかを記述し、「どのように」それを達成するかについてはシステムに任せるプログラミングパラダイムです。

歴史

宣言型プログラミングの概念は、1950年代と1960年代に初期のプログラミング言語とデータベースシステムで初めて導入されました。例えば、SQLは1970年代に宣言型のクエリ言語として開発されました。また、関数型言語や論理型言語も宣言型の一部と見なされます。

特徴

宣言型プログラミングの主な特徴は以下の通りです。

  1. 目標指向: プログラムは「何を」達成するべきかを記述し、具体的な手順(「どのように」)はシステムに任せます。
  2. 抽象化: 宣言型プログラミングは詳細な手順から抽象化されており、プログラマはより高いレベルで考えることができます。

メリット

宣言型プログラミングは、抽象的なレベルで考えることを可能にし、コードを短く、明確で、読みやすくすることができます。また、プログラムの「何を」に集中することで、プログラムの意図を明確にすることができます。

デメリット

一方、宣言型プログラミングの抽象度の高さは、デバッグやパフォーマンスチューニングを困難にする場合があります。また、宣言型プログラミングは、手続き型プログラミングとは異なる思考を必要とするため、学習曲線は急である場合があります。

宣言型プログラミングの例

// 宣言型プログラミングの例:フィルタリング

// リストから奇数だけを取り出すための宣言
oddNumbers = filter(list, isOdd)

// 'isOdd' は、値が奇数かどうかを確認する関数
function isOdd(number) {
    return number % 2 != 0
}

// 使用例
numbers = [1, 2, 3, 4, 5]
oddNumbers = filter(numbers, isOdd)

// 結果の出力
print(oddNumbers)  // [1, 3, 5]


並行/並列プログラミング

並行プログラミングと並列プログラミングは、プログラムのタスクを同時に実行するためのテクニックです。並行プログラミングは、一つのコア内でのマルチスレッドの管理を指し、並列プログラミングは、複数のコア、CPU、マシン間でタスクを同時に実行することを指します。

歴史

並行性と並列性の概念は、コンピュータシステムの初期から存在していましたが、これらの概念が主流になったのは、マルチコアプロセッサが一般的になった2000年代以降です。これらのテクニックは、言語レベルやライブラリレベルでサポートされてきました。例えば、Javaはスレッドとロック、Pythonはスレッドとプロセス、Goはゴルーチンとチャネルといった並行性のためのプリミティブを提供しています。

特徴

並行/並列プログラミングの主な特徴は以下の通りです。

  1. 並行プログラミング: 並行プログラミングでは、一度に一つのタスクだけが実行されますが、複数のタスクが「同時に」実行されているように見えます。これは、コンピュータがタスク間を非常に高速に切り替えることで実現されます。主な技術はマルチスレッディングと非同期プログラミングです。
  2. 並列プログラミング: 並列プログラミングでは、リソース(例えば、マルチコアプロセッサや分散システム)を利用して、実際に複数のタスクを同時に実行します。

メリット

並行性と並列性は、タスクの実行時間を短縮し、プログラムのパフォーマンスを向上させるのに役立ちます。また、非同期処理により、ユーザインターフェースが応答を続けるなど、プログラムの感じられるパフォーマンスも改善します。

デメリット

しかし、並行性と並列性はプログラミングをより複雑にします。データの競合やデッドロックなどの問題を解決するために、同期のメカニズムを適切に使用する必要があります。また、並列プログラムのデバッグは困難です。

並行プログラミングの例

// 並行プログラミングの例:スレッドの作成と使用

// 関数を定義
function printNumbers() {
    for (var i = 1; i <= 10; i++) {
        print(i)
    }
}

function printLetters() {
    for (var letter of 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') {
        print(letter)
    }
}

// 新しいスレッドを作成して関数を実行
thread1 = new Thread(printNumbers)
thread2 = new Thread(printLetters)

// スレッドを開始
thread1.start()
thread2.start()

// スレッドが終了するのを待つ
thread1.join()
thread2.join()