「ジェネリックプログラミング」の版間の差分
削除された内容 追加された内容
問題部分を削除して再掲載しました タグ: 差し戻し済み サイズの大幅な増減 ビジュアルエディター |
タグ: 取り消し サイズの大幅な増減 |
||
1行目:
'''ジェネリック'''(総称あるいは汎用)'''プログラミング'''({{lang-en-short|generic programming}})は、具体的なデータ型に直接依存しない、抽象的かつ汎用的なコード記述を可能にする[[プログラミング (コンピュータ)|コンピュータプログラミング]]手法である。
== 概要 ==
ジェネリックプログラミングは[[データ型]]でコードを[[インスタンス]]化するのか、あるいはデータ型をパラメータとして渡すかということにかかわらず、同じソースコードを利用できる<ref>{{Cite_web|url=http://msdn.microsoft.com/msdnmag/issues/05/04/PureC/|title=Pure C++:Generic Programming Under .NET|author=Stanley B. Lippman|publisher=[[マイクロソフト]]・[[MSDN]]マガジン|accessdate=2008-12-28|deadlinkdate=2019-03}}</ref>。ジェネリックプログラミングは言語により異なる形で実装されている。ジェネリックプログラミングの機能は1970年代に[[CLU]]や[[Ada]]のような言語に搭載され、次に[[BETA]]、[[C++]]、[[D言語|D]]、[[Eiffel]]、[[Java]]、その後[[ディジタル・イクイップメント・コーポレーション|DEC]]の[[Trellis/Owl]]言語などの数多くのオブジェクトベース (object-based) および[[オブジェクト指向]] (object-oriented) 言語に採用された。
1995年の書籍[[デザインパターン (ソフトウェア)|デザインパターン]]{{Full|date=2019年3月}}の共著者{{誰|date=2019年3月}}は(Ada、Eiffel、Java、[[C Sharp|C#]]の)ジェネリクスや、(C++の)[[テンプレート (プログラミング)|テンプレート]]としても知られるパラメータ化された型 (parameterized types) としてジェネリクスについて触れている。これらは、型を指定することなく、型を定義できるようにする(型は使用する時点で引数として与えられる)。このテクニック(特に[[デリゲート (プログラミング)|デリゲーション]]を組み合わせるとき)は非常に強力である。
== 特徴 ==
ジェネリックプログラミングの特徴は、型を抽象化してコードの再利用性を向上させつつ、[[静的型付け]]言語の持つ型安全性を維持できることである。
ジェネリックプログラミングを用いない場合、例えば伝統的な[[C言語]]や[[Pascal]]のような従来の[[静的型付け]]言語において、ソートなどのアルゴリズムや[[連結リスト]]のようなデータ構造(オブジェクトの[[コンテナ (データ型)|コンテナ]])を記述する際は、たとえ対象となる要素のデータ型が異なるだけで事実上同一のコードであったとしても、具体的なデータ型ごとにそれぞれ実装しなければならない。[[整数型]]のリスト、[[倍精度浮動小数点数]]型のリスト、[[文字列]]型のリスト、ユーザー定義[[構造体]]のリスト、……といった具合である。もしジェネリックプログラミングをサポートしない言語で汎用的なコードを記述して再利用しようと思えば、メモリ空間効率や型安全性などを犠牲にしなければならなくなる([[共用体]]や汎用[[ポインタ (プログラミング)|ポインタ]]型と[[型変換|キャスト]]を駆使するなど)。一方、C++の関数テンプレートやクラステンプレートのように、ジェネリックプログラミングを用いることで、抽象化された型について一度だけ記述したアルゴリズムやデータ構造をさまざまな具象データ型に適用して、コードを型安全に再利用できるようになる。これがジェネリックプログラミングの利点の一例として挙げられる。
以下にC++の例を示す。
<syntaxhighlight lang="cpp">
217 ⟶ 89行目:
-->
上記は要素型を<code>T</code>とする[[双方向連結リスト]]の定義例である。<code>typename T</code>はテンプレートによる抽象化の対象となる型の名前(プレースホルダー)を表す。そしてこの定義されたクラステンプレートの'''インスタンス化'''、すなわち型パラメータ<code>T</code>に具象型を与えることによって生成されるクラス型は、<code>T</code>について実際に指定した具象型のリストとして扱われる。これらの「T型のコンテナ」を一般に'''ジェネリクス''' (generics) と呼び、ジェネリックプログラミングの代表的なテクニックである。プログラミング言語によって制約は様々だが、このテクニックは、[[継承 (プログラミング)|継承]]関係や[[シグネチャ]]といった制約条件 (constraint) を維持する限り、内包する<code>T</code>にあらゆるデータ型を指定可能なクラスの定義を可能にする。これはジェネリックプログラミングの典型であり、一部の言語{{要説明|date=2019年3月}}ではこの形式のみを実装する。ただし、概念としてのジェネリックプログラミングはジェネリクスに限定されない。
オブジェクト指向プログラミング言語は、サブタイプ(派生型)でスーパータイプ(基底型)の振る舞い(アルゴリズム)を[[オーバーライド]]することによる動的な[[ポリモーフィズム]](多態性)を備えており、動的な多態性もまたスーパータイプによる抽象化とサブタイプによる具象化<ref>[[統一モデリング言語]] (UML) の用語では、それぞれ汎化 (generalization) および特化 (specialization) と呼ぶ。</ref>を実現するものだが、ジェネリクスは静的な多態性による抽象化と具象化を実現するという点で設計を異にする。
ジェネリックプログラミングのもう一つの応用例として、型に依存しないスワップ関数の例を示す。
<syntaxhighlight lang="cpp">
234 ⟶ 110行目:
</syntaxhighlight>
上記の例で使用したC++の<code>template</code>文は、プログラマーや言語の開発者たちにこの概念を普及させたジェネリックプログラミングの例といわれている。この構文はジェネリックプログラミングの全ての概念に対応する。またD言語はC++のテンプレートを基に構文を単純化した完全なジェネリックの機能を提供する。JavaはJ2SE 5.0よりC++の文法に近いジェネリックプログラミングの機能を提供しており、ジェネリクス(「T型のコンテナ」)という、ジェネリックプログラミングの部分集合を実装する。
C# 2.0、[[Microsoft Visual Basic .NET|Visual Basic .NET]] 2005 (VB 8.0) では、[[.NET Framework|Microsoft .NET Framework]] 2.0がサポートするジェネリクスを利用するための構文が追加された。[[ML (プログラミング言語)|ML]]ファミリーは{{仮リンク|パラメータ多相|en|Parametric polymorphism}} (parametric polymorphism) とファンクタと呼ばれるジェネリックモジュールを利用してのジェネリックプログラミングを推奨する。[[Haskell]]のタイプクラスのメカニズムもまたジェネリックプログラミングに対応する。
[[Objective-C]]にあるような[[動的型付け]]を使い、必要に応じて注意深くコーディング規約を守れば{{要説明|date=2019年3月}}、ジェネリックプログラミングの技術を使う必要がなくなる。全てのオブジェクトを包括する汎用型があるためである。Javaもまたそうであるが、キャストが必要なので静的な型付けの統一性を乱してしまう。例えば、ジェネリクスをサポートしていなかった時代のJavaでは、{{Javadoc:SE|java/util|List}}のようなコレクションに格納できる要素型は{{Javadoc:SE|java/lang|Object}}のみであったため、要素取り出しの際には実際のサブクラス型への適切なキャストが必要だった。それに対し、ジェネリクスは静的な型付けについての利点を持ちながら動的な型付けの利点を完全ではないが得られる方法である。
==Adaのジェネリクス==
Adaには1977年-1980年の設計当初から汎用体 (generics) が存在する。標準ライブラリでも多くのサービスを実装するために汎用体を用いている。Ada2005では1998年に規格化されたC++の[[Standard Template Library]] (STL) の影響を受けた広範な汎用コンテナが標準ライブラリとして追加された。
汎用体 (generic unit) とは、0または複数の汎用体仮パラメータ (generic formal parameters) を採るプログラム単位(パッケージまたは副プログラム)である。
<!-- en の"one or more"は誤り.仮パラメータを持たない汎用体も宣言できる.-->
汎用体仮パラメータとしては、オブジェクト(変数・定数)、データ型、副プログラム、パッケージ,さらには他の汎用体のインスタンスさえ指定することができる。汎用体仮パラメータのデータ型としては、離散 (discrete) 型、[[浮動小数点数]]型、[[固定小数点数]]型、アクセス([[ポインタ (プログラミング)|ポインタ]])型などを用いることができる。
汎用体をインスタンス化する際、プログラマは全ての仮パラメータに対応する実パラメータを指定する必要があるが、プログラマが明示的に全ての実パラメータを指定しなくても済むよう,仮パラメータにはデフォルトを指定することもできる。インスタンス化してしまえば,汎用体のインスタンスは、汎用体ではない通常のプログラム単位であるかのように振舞う。インスタンス化は実行時、例えばループの中などで行うことも可能である。
===Adaの例===
汎用体パッケージの仕様部
<syntaxhighlight lang="ada">
generic
Max_Size : Natural; -- 汎用体仮オブジェクトの例
type Element_Type is private; -- 汎用体仮データ型の例; この例では制限型でなければ任意のデータ型が該当
package Stacks is
type Size_Type is range 0 .. Max_Size;
type Stack is limited private;
procedure Create (S : out Stack;
Initial_Size : in Size_Type := Max_Size);
procedure Push (Into : in out Stack; Element : in Element_Type);
procedure Pop (From : in out Stack; Element : out Element_Type);
Overflow : exception;
Underflow : exception;
private
subtype Index_Type is Size_Type range 1 .. Max_Size;
type Vector is array (Index_Type range <>) of Element_Type;
type Stack (Allocated_Size : Size_Type := 0) is record
Top : Index_Type;
Storage : Vector (1 .. Allocated_Size);
end record;
end Stacks;
</syntaxhighlight>
汎用体パッケージのインスタンス化
<syntaxhighlight lang="ada">
type Bookmark_Type is new Natural;
-- 編集中のテキストドキュメント内の場所を記録する
package Bookmark_Stacks is new Stacks (Max_Size => 20,
Element_Type => Bookmark_Type);
-- ドキュメント中の記録された場所にユーザがジャンプできるようにする
</syntaxhighlight>
汎用体パッケージインスタンスの利用
<syntaxhighlight lang="
type Document_Type is record
Contents : Ada.Strings.Unbounded.Unbounded_String;
Bookmarks : Bookmark_Stacks.Stack;
end record;
procedure Edit (Document_Name : in String) is
Document : Document_Type;
begin
-- ブックマークのスタックを初期化
Bookmark_Stacks.Create (S => Document.Bookmarks, Initial_Size => 10);
-- この時点でDocument_Nameファイルを開いたり、読み込んだりが可能
end Edit;
</syntaxhighlight>
===利点と制限===
Adaの言語構文では、汎用体仮パラメータとして何を許容するか、精密に制約条件を課することができる。例えば実パラメータとしてはモジュラー型(任意の上限で巡回する符号なし整数型)のみを許容するように、仮パラメータとして指定することも可能である。さらには汎用体仮パラメータ間に一定の制約があるように規制することも可能である。例えば、
<syntaxhighlight lang="ada">
generic
type Index_Type is (<>); -- 離散型(discrete type)のみを許容
type Element_Type is private; -- 制限型(limited type)以外の任意データ型
type Array_Type is array (Index_Type range <>) of Element_Type;
</syntaxhighlight>
この例でArray_Typeには、Element_Typeに対応する特定のデータ型を要素とし、Index_Typeに対応する特定の離散型の部分型を添字とする配列型でなければならないという制約を課している。プログラマがこの汎用体をインスタンス化する際には、同制約を満足する配列型を実パラメタとして渡さなければならない。
構文の複雑さに難はあるものの、精密な制約が表現できることで、汎用体仮パラメータの全ては仕様部として完全に定義される。このため、コンパイラは汎用体本体がなくても汎用体をインスタンス化することができる(もちろん本体がないと[[リンケージエディタ|リンク]]はできない)。
C++と異なってAdaでは暗黙的な特化による汎用体のインスタンス化を許さないため、全ての汎用体は明示的にインスタンス化することが必要である。この規則により以下のような結果が生じる。
*コンパイラは共有ジェネリクス (shared generics) を実装できる。すなわち、ある汎用体のオブジェクトコードは全インスタンスで共有できる(もちろんプログラマが副プログラムのインライン化を要求しない限り)。さらなる結果として、
**コードが肥大化する可能性がない(コードの肥大化はC++では一般的であり後述のように特別な配慮が求められる)。
**インスタンス化の都度に新たなオブジェクトコードを生成することは不要であるため、コンパイル時のみならず、実行時に汎用体をインスタンス化することができる。
**汎用体仮オブジェクトに対応する実オブジェクトは、たとえ同実オブジェクトが静的である(コンパイル時に値が確定する)としても、汎用体本体中では常に静的ではないものとみなされる。詳細についてはWikibookのGeneric formal objectsを参照。
*ある汎用体の全インスタンスは全く同一であるため、他人の作成したプログラムをレビューしたり、理解することが容易である。配慮すべき「特別な場合」はないのだから。
*全てのインスタンス化は明示的であり、プログラムの理解が困難となるような暗黙的なインスタンス化はない。
*Adaでは特化を許容しないため[[テンプレートメタプログラミング]]はできない。
:ただし仮パラメータに精密な制約を課することができるため、例えば、スワップ副プログラムを仮パラメータとして、[[ソート]]を目的とした汎用体の挙動をスワップ対象に応じて変化させたり、離散型の規定演算である大小判定を用いてMaxを実装するなど、特化の利点とされる目的の一部は他の方法により達成することができる。
==C++のテンプレート==
{{main|テンプレート (プログラミング)}}
C++のテンプレートは関数テンプレート、クラステンプレートをサポートするほか、[[C++14]]では変数テンプレートもサポートするようになった。C++のテンプレートは特に静的な[[ダック・タイピング]]を可能にする点で強力であり、JavaやC#のジェネリクスと比べて柔軟性が高い一方、テンプレート引数に関する制約条件を明示的にコード上で記述できないことからコンパイルエラーメッセージが難解になりやすい。テンプレートはC++言語仕様の複雑化の要因にもなっている。
C++の[[Standard Template Library]] (STL) はテンプレートによる汎用的なアルゴリズムとデータ構造を提供する。
==D言語のテンプレート==
D言語はC++のものを発展させたテンプレートをサポートする。大半のC++テンプレートの表現はD言語でもそのまま利用できる。それに加え、D言語は一部の一般的なケースを合理化する機能をいくつか追加する。
最もはっきりとした違いは一部のシンタックスの変更である。D言語はテンプレートの定義で山形カッコ<code>< ></code>の代わりに丸カッコ<code>( )</code>を使用する。またテンプレートのインスタンス化でも山形カッコの代わりに<code>!( )</code>構文(感嘆符を前に付けた丸カッコ)を使う。従って、D言語の<code>a!(b)</code>はC++の<code>a<b></code>と等価である。この変更は、テンプレート構文の[[構文解析]]を容易にするためになされた(山形カッコは比較演算子との区別がつきにくく、構文解析器が複雑化しがちであった)。
===Static-if===
D言語はコンパイル時に条件をチェックする<code>static if</code>構文を提供する。これはC++の<code>#if</code>と<code>#endif</code>のプリプロセッサマクロに少し似ている。<code>static if</code>はテンプレート引数や、それらを使用したコンパイル時関数実行の結果を含めた全てのコンパイル時の値にアクセスできるというのがその主要な違いである。従ってC++でテンプレートの特殊化を必要とする多くの状況でも、D言語では特殊化の必要なく容易に書ける。D言語の再帰テンプレートは通常の実行時再帰とほぼ同じように書ける。これは典型的なコンパイル時の関数テンプレートに見られる。
<syntaxhighlight lang="d">
311 ⟶ 228行目:
const Factorial = n * Factorial!(n - 1);
}
</syntaxhighlight>
===エイリアスパラメータ D言語のテンプレートはまたエイリアスパラメーターを受け入れることができる。エイリアスパラメーターはC++の<code>typedef</code>と似ているが、テンプレートパラメーターを置き換えることもできる。これは今後利用可能なC++0x仕様に追加されるであろう、C++のテンプレートのテンプレート引数にある機能の拡張版である。エイリアスパラメーターは、テンプレート、関数、型、その他のコンパイル時のシンボルを指定できる。これは例えばテンプレート関数の中に関数をプログラマーが''挿入''できるようにする。
<syntaxhighlight lang="d">
322 ⟶ 242行目:
</syntaxhighlight>
この種のテンプレートはC言語APIとD言語のコードを接続するときに使いやすいだろう。仮想のC言語APIが関数ポインタを要求する場合、このようにテンプレートを利用できる。
<syntaxhighlight lang="d">
331 ⟶ 251行目:
some_c_function(&wrapper!(foo));
</syntaxhighlight>
==Javaのジェネリクス==
2004年、[[Java Platform, Standard Edition|J2SE]] 5.0の一部として[[Java]]にジェネリクスが追加された。C++のテンプレートとは違い、Javaコードのジェネリクスはジェネリッククラスの1つのコンパイルされたバージョンだけを生成する。ジェネリックJavaクラスは型パラメータとしてオブジェクト型だけを利用できる(基本型は許されない)。従って<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|Integer}}></code>は正しいのに対して<code>{{Javadoc:SE|java/util|List}}<int></code>は正しくない。
Javaではジェネリクスはコンパイル時に型の正しさをチェックする。そしてジェネリック型情報は[[型消去]] (type erasure) と呼ばれるプロセスを通じて除去され、親クラスの型情報だけが保持される。例えば、<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|Integer}}></code>は全てのオブジェクトを保有できる非ジェネリックの(生の){{Javadoc:SE|java/util|List}}に変換されるだろう。しかしながら、コンパイル時のチェックにより、コードが未チェックのコンパイルエラーを生成しない限り、型が正しいようにコードの出力が保証される。
このプロセスの典型的な副作用はジェネリック型の情報を実行時に参照できないことである。従って、実行時には、<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|Integer}}></code>と<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|String}}></code>が同じ{{Javadoc:SE|java/util|List}}クラスであることを示す。この副作用を緩和するひとつの方法は{{Javadoc:SE|java/util|Collection}}の宣言を修飾するJavaの{{Javadoc:SE|name=Collections.checkedList(List<E>, Class<E>)|java/util|Collections|checkedList-java.util.List-java.lang.Class-}}メソッドを利用して、実行時に型付けされた{{Javadoc:SE|java/util|Collection}}の不正利用(例えば不適切な型の挿入)をチェックすることによるものである。これは旧式のコードとジェネリクスを利用するコードを共存運用したい場合の状況で役立つ。
C++やC#のように、Javaはネストされたジェネリック型を定義できる。従って、例えば<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/util|Map}}<{{Javadoc:SE|java/lang|Integer}}, {{Javadoc:SE|java/lang|String}}>></code>は有効な型である。
===ワイルドカード===
Javaのジェネリック型パラメーターは特定のクラスに制限されない。与えられたジェネリックオブジェクトが持っているかもしれないパラメーターの型の境界を指定するためにJavaでは'''ワイルドカード'''を使用できる。例えば、<code>{{Javadoc:SE|java/util|List}}<?></code>は無名のオブジェクト型を持つリストを表す。引数として<code><nowiki>List<?></nowiki></code>を取るようなメソッドは任意の型のリストを取ることができる。リストからの読み出しは{{Javadoc:SE|java/lang|Object}}型のオブジェクトを返し、そしてnullではない要素をリストへ書き込むことはパラメーター型が任意ではないために許されない。
ジェネリック要素の制約を指定するために、ジェネリック型が境界クラスのサブクラス(クラスの拡張と[[インタフェース (抽象型)|インターフェイス]]の実装のいずれか)であることを示すキーワード<code>extends</code>を使用できる。そして<code>{{Javadoc:SE|java/util|List}}<? extends {{Javadoc:SE|java/lang|Number}}></code>は与えられたリストが{{Javadoc:SE|java/lang|Number}}クラスを拡張するオブジェクトを保持することを意味する。従って、リストが何の要素の型を保持しているのかがわからないためにnullではない要素の書き込みが許されないのに対し、リストから要素を読むと{{Javadoc:SE|java/lang|Number}}が返るだろう。
ジェネリック要素の下限を指定するために、ジェネリック型が境界クラスのスーパークラスであることを示すキーワード<code>super</code>が使用される。そして<code>{{Javadoc:SE|java/util|List}}<? super {{Javadoc:SE|java/lang|Number}}></code>は<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|Number}}></code>や<code>{{Javadoc:SE|java/util|List}}<{{Javadoc:SE|java/lang|Object}}></code>でありえる。リストに正しい型を保存することが保証されるため任意の{{Javadoc:SE|java/lang|Number}}型の要素をリストに追加できるのに対し、リストからの読み出しでは{{Javadoc:SE|java/lang|Object}}型のオブジェクトを返す。
=== 制約 ===
Javaのジェネリクスの実装上の制約により、配列のコンポーネントの型が何であるべきかを特定する方法がないために、ジェネリック型の配列を作成することは不可能である。従って<code><nowiki>new T[size];</nowiki></code>経由のようにメソッドが型引数<code>T</code>を持っていた場合はプログラマはその型の新しい配列を生成することができない。しかし、この制約はJavaの[[リフレクション (情報工学)|リフレクション]]のメカニズムを利用して回避することが可能である。クラス<code>T</code>のインスタンスが利用可能な場合、<code>T</code>に対応する{{Javadoc:SE|java/lang|Class}}オブジェクトのオブジェクトから1つを得て、新しい配列を生成するために{{Javadoc:SE|name=java.lang.reflect.Array.newInstance(Class, int)|java/lang/reflect|Array|newInstance-java.lang.Class-int-}}を使うことができる。もう1つのJavaのジェネリクスの実装上の制約は、<code><?></code>以外に、型パラメーターの型でジェネリッククラスの配列を生成することが不可能であるということだ。これは言語の配列の取り扱い方法に起因するものであり、タイプセーフを維持するために、明示的にキャストしなくともコンパイラが警告を出さないことを全てのコードで保証する必要があるからである。
==Haskellのジェネリックプログラミング==
[[Haskell]]言語にはパラメータ化された型 (parameterized types)、パラメータ多相 (parametric polymorphism)、そしてJavaのジェネリクスやC++のテンプレートの両方に似たプログラミングのスタイルをサポートする型クラス (type classes) がある。Haskellプログラムではこれらの構文を様々なところで利用しており、避けることはかなり難しい。Haskellはまた、さらなるジェネリック性と、多態が提供する以上の再利用性を目指すようにプログラマーと言語開発者を奮起させる、さらに独特なジェネリックプログラミングの機能がある。
Haskellの6つの事前定義された型クラス(同一性を比較できる<code>Eq</code>という型と、値を文字列に変換できる<code>Show</code>という型を含む)は''導出インスタンス'' (derived instances) をサポートしている特別なプロパティを持つ。プログラマーが新しい型を定義するということは、クラスのインスタンスを宣言するときに、普通であれば必要なクラスメソッドの実装を提供することなく、この型がこれらの特別型クラスのインスタンスとなることを明示できるということである。全ての必要なメソッドは型の構造に基づいて導出(つまり自動的に生成)される。
例として、下記の[[二分木]]型の宣言はこれが<code>Eq</code>と<code>Show</code>のクラスのインスタンスになることを示している。
<pre>
data BinTree a = Leaf a | Node (BinTree a) a (Bintree a)
deriving (Eq, Show)
</pre>
<code>T</code>がそれらの演算子を自分でサポートしているのであれば、任意の型の<code>BinTree T</code>形式のために比較関数 (<code>==</code>) と文字列表現関数 (<code>show</code>) が自動的に定義される。
<code>Eq</code>と<code>Show</code>の導出インスタンスへのサポートは、それらのメソッドである<code>==</code>と<code>show</code>を、パラメーター的な多態関数とは質的に異なるジェネリックにする。これらの"関数"(より正確には型でインデックス付けられた (type-indexed) 関数のファミリー)はたくさんの異なる型の値を受け入れることができ、各引数の型によってそれらは異なる動作をするが、新しい型へのサポートを追加するためにわずかな作業が必要とされる。Ralf Hinze氏 (2004) は、あるプログラミングテクニックによりユーザー定義型のクラスに対して同様の結果を達成できることを示した。彼以外の多くの研究者はこれと、Haskellの流れとは違う種類のジェネリック性やHaskellの拡張(下記参照)に対する取り組みを提案していた。
=== PolyP ===
PolyPはHaskellに対する最初のジェネリックプログラミング言語拡張であった。PolyPではジェネリック関数は''polytypic''と呼ばれた。通常データ型のパターン[[ファンクタ]]の構造によって構造的な導出を通じて定義できるpolytypic関数のような特別な構文を言語に導入した。PolyPでの通常データ型はHaskellのデータ型のサブセットである。通常データ型tは''* → *''の種類でなければならず、もし''a''が定義における表面的な型の引数である場合は、''t''に対する全ての再帰呼び出しは''t a''形式でなければならない。これらの制約は、異なる形式の再帰呼び出しである入れ子のデータタイプと同様に、上位に種類付けされたデータ型を規定する。
PolyPの展開された関数はここに例として示される。
<pre>
flatten :: Regular d => d a -> [a]
flatten = cata fl
polytypic fl :: f a [a] -> [a]
case f of
g+h -> either fl fl
g*h -> \(x,y) -> fl x ++ fl y
() -> \x -> []
Par -> \x -> [x]
Rec -> \x -> x
d@g -> concat . flatten . pmap fl
Con t -> \x -> []
cata :: Regular d => (FunctorOf d a b -> b) -> d a -> b
</pre>
===ジェネリックHaskell===
ジェネリックHaskellは[[ユトレヒト大学]]で開発されたHaskellのもう1つの拡張だ。この拡張は下記の特徴がある。
*''Type-indexed values''は様々なHaskell型のコンストラクタ(ユニット、基本型、合計、積、ユーザー定義型のコンストラクタ)に渡ってインデックス付けられた値として定義される。さらに''コンストラクタケース''を使って特定のコンストラクタに対してtype-indexed valuesの動作を指定することもでき、''デフォルトケース''を使ったもう一つの中で1つのジェネリック定義を再利用することもできる。
type-indexed valueの結果は任意の型に特殊化され得る。
*''Kind-indexed types''は''*''と''k → k''の両方のケースを与えることで定義された種別に対してインデックス付けられた型である。インスタンスは種別にkind-indexed typeを適用することで得られる。
*ジェネリック定義は型もしくは種別にそれらを適用することで利用できる。これは''ジェネリックアプリケーション''と呼ばれる。どの種類のジェネリック定義が適用されたかに依存して結果は型か値になる。
*''Generic abstraction''はジェネリック定義が(与えられた種別の)型パラメーターの抽象化で定義されることを可能にする。
*''Type-indexed types''は型コンストラクタに対してインデックス付けられた型である。これらは型がもっとジェネリック値に取り入るために利用できる。type-indexed typesの結果は任意の型に特殊化され得る。
ジェネリックHaskellの比較関数の一例として。
<pre>
type Eq {[ * ]} t1 t2 = t1 -> t2 -> Bool
type Eq {[ k -> l ]} t1 t2 = forall u1 u2. Eq {[ k ]} u1 u2 -> Eq {[ l ]} (t1 u1) (t2 u2)
eq {| t :: k |} :: Eq {[ k ]} t t
eq {| Unit |} _ _ = True
eq {| :+: |} eqA eqB (Inl a1) (Inl a2) = eqA a1 a2
eq {| :+: |} eqA eqB (Inr b1) (Inr b2) = eqB b1 b2
eq {| :+: |} eqA eqB _ _ = False
eq {| :*: |} eqA eqB (a1 :*: b1) (a2 :*: b2) = eqA a1 a2 && eqB b1 b2
eq {| Int |} = (==)
eq {| Char |} = (==)
eq {| Bool |} = (==)
</pre>
===「決まり文句を捨てる」アプローチ===
決まり文句を捨てるアプローチ (Scrap your boilerplate approach) は簡易的なジェネリックプログラミングのHaskellに対するアプローチである (Lämmel and Peyton Jones, 2003)。このアプローチはHaskellのGHC>=6.0の実装でサポートされる。このアプローチを使うことで、ジェネリックな読み込み、ジェネリックな明示、ジェネリックな比較(つまりgread、gshow、geq)と同様に、横断スキーム(例えばいつでもどこでも)のようなジェネリック関数をプログラマーは記述できる。このアプローチはタイプセーフなキャストとコンストラクタアプリケーションの実行のための一部の基本要素に基づいている。
==C#と.NETのジェネリックプログラミング==
C#(およびその他の.NET言語)のジェネリクスは.NET Framework 2.0の一部として2005年11月に追加された。Javaと似てはいるが、.NETのジェネリクスは、コンパイラによるジェネリクス型から非ジェネリクス型へのコンバートとしてではなく、実行時に実装される。このことにより、ジェネリクス型に関するあらゆる情報はメタデータとして保存される。
.NETジェネリクスの機能
*型情報を削除せず、[[共通言語ランタイム|CLR]]の内部でジェネリクスが構築されるため(そしてコンパイラ上では全く構築しないため)、キャストや動的チェックの実行からくるパフォーマンスヒットがない。また、プログラマーはリフレクションを通じてジェネリック情報にアクセスできる。
**型情報を削除しないので、Javaでは不可能なジェネリック型の配列の生成が可能。
*ジェネリック型の引数として参照型だけでなく値型(組み込みの基本型、およびユーザー定義型の両方)も利用できる。値型の場合、JITコンパイラは特殊化のためにネイティブコードの新しいインスタンスを作成する。このことにより[[ボックス化]]をする必要がなくなり、パフォーマンスが向上する。
*Javaと同様、ジェネリック型引数がそれら自身のジェネリック型であるようにできる。つまり、<code><nowiki>List<List<Dictionary<int, int>>></nowiki></code>のような型は有効である。
*C#(および一般の.NET)は、キーワード<code>where</code>を使用することで、値型/参照型、デフォルトコンストラクタの存在、親クラス、実装するインターフェイスなどでジェネリック型を制約することができる。
*[[共変性と反変性 (計算機科学)|共変性と反変性]]をサポートしている。C# 4.0以降ではout修飾子またはin修飾子により、型パラメータを共変または反変にすることができる。これによって、ジェネリック型の代入と使用の柔軟性が向上する。
<syntaxhighlight lang="csharp">
using System;
using System.Collections.Generic;
static int FirstIndexOfMax<T>(List<T> list) where T: IComparable<T>
{
if (list.Count == 0) {
return -1;
}
int index = -1;
for (int i = 0; i < list.Count; ++i) {
if ((index == -1 && list[i] != null) ||
(index >= 0 && list[index] != null && list[i] != null && list[index].CompareTo(list[i]) < 0)) {
index = i;
}
}
return index;
}
</syntaxhighlight>
この例では<code>FirstIndexOfMax</code>メソッドの型パラメータ<code>T</code>に対して、<code><nowiki>IComparable<T></nowiki></code>インターフェイスを実装していなければならないという制約を指定している。このことにより、<code><nowiki>IComparable<T></nowiki></code>インターフェイスのメンバである<code>CompareTo</code>メソッドが利用可能になっている。
[[C++/CLI]]は.NETのジェネリクスとC++のテンプレート両方をサポートする。ただしこれらの間に互換性はない。
==その他の言語のジェネリックプログラミング機能==
数多くの関数型言語はパラメータ化された型 (parameterized types) とパラメータ多相 (parametric polymorphism) の形で小規模なジェネリックプログラミングをサポートする。さらに標準MLとOCamlはクラステンプレートとAdaのジェネリックパッケージに似たファンクタを提供する。
[[Verilog]]のモジュールは1つ以上のパラメタを取ることができる。パラメタの実際の値は、そのモジュールを実体化する際に与えられる。一例としてジェネリックな[[:en:Hardware register|レジスタ]]アレイがあり、アレイの幅がパラメタで与えられている。そのようなアレイをジェネリックなワイヤベクトルと組み合わせることにより、単一のモジュール実装を用いて任意のビット幅を持つジェネリックなバッファやメモリを作ることができる。<ref>Verilog by Example, Section ''The Rest for Reference''. Blaine C. Readler, Full Arc Press, 2011. ISBN 978-0-9834973-0-1</ref>
== 脚注 ==
336 ⟶ 384行目:
== 関連項目 ==
* [[総称型]]
* [[ポリモーフィズム]]
* [[ダック・タイピング]]
{{DEFAULTSORT:しえねりつくふろくらみんく}}
[[Category:ソフトウェア工学]]
|