ジェネリックプログラミング

ジェネリックプログラミングは、具体的なデータ型に直接依存せず、抽象的かつ汎用的なコード記述を可能にするプログラミング手法です。

概要


ジェネリックプログラミングは、データ型でコードをインスタンス化するか、データ型をパラメータとして渡すかにかかわらず、同じソースコードを利用できます。この手法は、1970年代にCLUやAdaなどの言語で導入され、その後、BETA、C++、D、EiffelJavaといった多くのオブジェクトベースおよびオブジェクト指向言語に採用されました。1995年の書籍『デザインパターン』では、ジェネリクスはパラメータ化された型として言及され、型を指定せずに定義できるため、非常に強力なテクニックとして紹介されました。

特徴


ジェネリックプログラミングの主な特徴は、型を抽象化することでコードの再利用性を高めつつ、静的型付け言語の型安全性を維持できる点です。例えば、従来のC言語Pascalでは、ソートアルゴリズムや連結リストなどのデータ構造を実装する際、データ型が異なるだけで同じようなコードを複数書く必要がありました。しかし、ジェネリックプログラミングを用いることで、一度記述したアルゴリズムやデータ構造を様々なデータ型に適用でき、コードの重複を避けることができます。

例えば、C++の関数テンプレートやクラステンプレートは、ジェネリックプログラミングの代表例です。これにより、抽象化された型に対して一度だけ記述したコードを、様々な具体的なデータ型に適用できます。以下は、C++での双方向連結リストの例です。

cpp
template
class DoublyLinkedList {
// ...
};


上記の例では、`typename T`が型パラメータであり、このテンプレートをインスタンス化する際に、具体的な型が指定されます。このようにして生成される「T型のコンテナ」がジェネリクスと呼ばれ、ジェネリックプログラミングの基本的なテクニックです。

オブジェクト指向プログラミングでは、動的なポリモーフィズム(多態性)が用いられますが、ジェネリックプログラミングは静的な多態性を提供します。また、型に依存しないスワップ関数の例も、ジェネリックプログラミングの応用例として挙げられます。

cpp
template
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}


C++の`template`文は、この概念を広めた代表的な例であり、D言語Javaもジェネリックプログラミングの機能を提供しています。C#やVisual Basic .NETも.NET Frameworkのジェネリクスを利用するための構文を提供しており、MLファミリーやHaskellもジェネリックプログラミングをサポートしています。

Adaのジェネリクス


Adaは設計当初からジェネリクスをサポートしており、標準ライブラリでも多く利用されています。Adaのジェネリクスは、汎用体(generic unit)と呼ばれ、オブジェクト、データ型、副プログラム、パッケージなどをパラメータとして受け取れます。汎用体は、インスタンス化することで、通常のプログラム単位のように使用できます。

ada
  • -- 汎用体パッケージの仕様部
generic
type Element_Type is private;
package Generic_List is
type List is private;
procedure Append(List : in out List; Item : in Element_Type);
-- ...
private
type List is record
-- ...
end record;
end Generic_List;

package Integer_List is new Generic_List(Element_Type => Integer);

My_List : Integer_List.List;
Integer_List.Append(My_List, 10);


Adaでは、汎用体仮パラメータに制約を課すことができ、コンパイラは汎用体本体がなくてもインスタンス化できます。また、共有ジェネリクスを実装でき、コードの肥大化を防ぐことができます。ただし、特化を許容しないため、テンプレートメタプログラミングはできません。

C++のテンプレート


C++のテンプレートは、関数テンプレート、クラステンプレート、変数テンプレートをサポートします。静的なダックタイピングを可能にする点が強力ですが、制約条件を明示的に記述できないため、コンパイルエラーが難解になりやすいという欠点もあります。

D言語のテンプレート


D言語C++のテンプレートを基に、構文を単純化した完全なジェネリック機能を提供しています。山形カッコの代わりに丸カッコを使用するなど、構文の変更も行われています。また、コンパイル時に条件をチェックする`static if`構文や、エイリアスパラメーターを受け入れる機能も備えています。

Javaのジェネリクス


JavaはJ2SE 5.0からジェネリクスを導入しました。C++のテンプレートとは異なり、コンパイル時に型情報を削除する型消去 (type erasure) が行われます。ジェネリック型パラメータとしてオブジェクト型のみを利用できます。ワイルドカードを使用することで、パラメータの型境界を指定することも可能です。Javaでは、ジェネリック型の配列を作成することはできません。

Haskellのジェネリックプログラミング


Haskellは、パラメータ化された型、パラメータ多相、型クラスをサポートしています。特に、`Eq`や`Show`といった型クラスは、型の構造に基づいて自動的にインスタンスを導出する機能を持っています。PolyPやGeneric Haskellといった拡張機能も提供されています。

C#と.NETのジェネリックプログラミング


C#のジェネリクスは、.NET Framework 2.0の一部として実装されました。Javaとは異なり、型情報を削除せず、実行時に実装されます。これにより、リフレクションを通じてジェネリック情報にアクセスでき、値型もジェネリック型引数として利用できます。共変性や反変性もサポートされており、より柔軟なジェネリック型を使用できます。

その他の言語のジェネリックプログラミング機能


関数型言語ではパラメータ化された型やパラメータ多相が一般的な機能です。Verilogではモジュールがパラメータを取ることができ、ジェネリックなハードウェア記述が可能です。

まとめ


ジェネリックプログラミングは、コードの再利用性、型安全性を向上させる重要なプログラミング手法です。各言語での実装には違いがありますが、基本的な概念は共通しており、様々なプログラミングパラダイムにおいて活用されています。

もう一度検索

【記事の利用について】

タイトルと記事文章は、記事のあるページにリンクを張っていただければ、無料で利用できます。
※画像は、利用できませんのでご注意ください。

【リンクついて】

リンクフリーです。