「ジェネリックプログラミング」の版間の差分

削除された内容 追加された内容
Sycgln (会話 | 投稿記録)
英語版の記事en:Generic programmingの無断転載と思われる記述をすべてリバート。著作権の侵害。Wikipedia:翻訳のガイドライン, Wikipedia:ウィキペディア内でのコピー
タグ: 手動差し戻し
1行目:
{{著作権問題調査依頼|date=2021-02}}
{{プログラミング・パラダイム}}'''ジェネリックプログラミング'''({{lang-en-short|generic programming}})は、プログラム内で扱われる[[データ型]]の詳細を、そのデータ型が[[インスタンス|実体化]]される時に決定するという方針によって、多様なデータ型を包括できる汎用的な[[アルゴリズム]]の記述を可能にするためのプログラミング手法である。この手法は、アルゴリズムと[[データ構造]]の機能的な抽象化と分類化を促進させて、プログラムの拡張性と保守性を向上させる<ref>{{cite book|author1=Alexander Stepanov|author2=Paul McJones|title=Elements of Programming|publisher=Addison-Wesley Professional|date=19 June 2009|isbn=978-0-321-63537-2}}</ref>。
'''ジェネリック'''(総称あるいは汎用)'''プログラミング'''({{lang-en-short|generic programming}})は、具体的なデータ型に直接依存しない、抽象的かつ汎用的なコード記述を可能にする[[プログラミング (コンピュータ)|コンピュータプログラミング]]手法である。
 
== 概要 ==
この手法で扱われる型は一般的に総称型(generic type)と呼ばれ、通常の型を部分的に抽象化したものと解釈されており、その抽象化された部分としての型変数を1個以上内包している。その型変数の詳細は、実体化の時に与えられる型パラメータによって決定される。これは{{仮リンク|パラメトリック多相|en|Parametric polymorphism}}に似ているが、その亜流の方で解釈されるジェネリックプログラミングは、総称的データ構造と汎用的アルゴリズムの連携を主体にしており、[[反復子]]の用法にも重点を置いている。
ジェネリックプログラミングは[[データ型]]でコードを[[インスタンス]]化するのか、あるいはデータ型をパラメータとして渡すかということにかかわらず、同じソースコードを利用できる<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) としてジェネリクスについて触れている。これらは、型を指定することなく、型を定義できるようにする(型は使用する時点で引数として与えられる)。このテクニック(特に[[デリゲート (プログラミング)|デリゲーション]]を組み合わせるとき)は非常に強力である。
== 歴史 ==
ジェネリックプログラミングは、計算機科学者{{仮リンク|デビッド・マッサー|en|David Musser|label=}}と{{仮リンク|アレクサンダー・ステパノフ|en|Alexander Stepanov|label=}}の1989年著書で確立されている。
定型プログラムの抽象化に焦点を当てているジェネリックプログラミングは、多様なデータ表現を結合させる汎用性の獲得によって従来アルゴリズムの効率性を高めて、ソフトウェアの多様性を促進させる<ref>{{cite book|url=http://stepanovpapers.com/genprog.pdf|title=Generic Programming|author1=Musser, David R.|author2=Stepanov, Alexander A.}}</ref>。
このパラダイムは、[[アルゴリズム]]と[[データ構造]]の連携で扱われている基礎要素を抽象化することでプログラムの汎用性および再利用性を高めることを提唱しており<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>、[[抽象代数学|抽象代数]]理論との類似性も見られる<ref>{{cite book|author1=Alexander Stepanov|author2=Paul McJones|title=Elements of Programming|publisher=Addison-Wesley Professional|date=19 June 2009|isbn=978-0-321-63537-2}}</ref>。このパラダイムのルーツは、計算機科学者[[クリストファー・ストレイチー]]の1967年著書にある{{仮リンク|パラメトリック多相|en|Parametric polymorphism}}であり、こちらは「[[ML (プログラミング言語)|ML]]」などの[[関数型プログラミング|関数型言語]]で1970年代から実践されている。このパラダイムに相当する機能は、1970年代からの「[[Scheme]]」「[[CLU]]」「[[Ada]]」「[[Eiffel]]」などがジェネリクスやジェネリシティの名称ですでに導入しており<ref>{{cite journal|year=1987|title=A library of generic algorithms in Ada|journal=Proceedings of the 1987 Annual ACM SIGAda International Conference on Ada|pages=216–225|doi=10.1145/317500.317529|isbn=0897912438|author1=Musser, David R.|author2=Stepanov, Alexander A.|citeseerx=10.1.1.588.7431|s2cid=795406}}</ref>、マッサーとステパノフによる確立は言わばそれらの後付け理論であったが、専用の用語による正規の形式化は、特に[[オブジェクト指向プログラミング]]への応用を促進させた<ref>{{cite book|url=http://www.cse.chalmers.se/~patrikj/poly/afp98/genprogintro.pdf|title=Generic Programming – an Introduction|author1=Roland Backhouse|author2=Patrik Jansson|author3=Johan Jeuring|author4=Lambert Meertens|year=1999}}</ref>。[[ポリモーフィズム]]理論で説明されるジェネリックプログラミングは、パラメトリック多相とはやや異なるポリタイピック (多相型) の方に分類されて[[圏論]]との親和性も認識された。
 
ジェネリックプログラミングは「[[C++]]」で静的特化に加工されて[[テンプレートメタプログラミング|テンプレート・メタプログラミング]]になり、[[標準テンプレートライブラリ]] (STL) として実装された<ref>Alexander Stepanov and Meng Lee: The Standard Template Library. HP Laboratories Technical Report 95-11(R.1), 14 November 1995</ref>。オブジェクト指向のアルゴリズムで重視されるようになった[[イテレータ|イテレーション]]の概念もここで確立されている<ref>Matthew H. Austern: Generic programming and the STL: using and extending the C++ Standard Template Library. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, USA 1998</ref>。ステパノフはこのように述べている。
ジェネリックプログラミングは、アルゴリズムとデータ構造の抽象化と分類体系化を推し進める。このインスパイアは[[ドナルド・クヌース|クヌース]]([[文芸的プログラミング]])からであり、[[型理論]]ではない<ref>{{cite book|url=http://stepanovpapers.com/history%20of%20STL.pdf|title=Short History of STL|author=Stepanov, Alexander}}</ref>。その目標は、抽象化されたアルコリズムとデータ構造の体系的なカタログ化(=再利用性)による進歩的なソフトウェア構築である<ref name="stroustrup2007">{{cite book|url=http://www.stroustrup.com/hopl-almost-final.pdf|title=Evolving a language in and for the real world: C++ 1991-2006|author=Stroustrup, Bjarne|doi=10.1145/1238844.1238848|s2cid=7518369}}</ref>。
また、[[イテレータ]]についてはこのように強調している。
イテレータの理論は、数学での[[環論]]や[[バナッハ空間]]のような、計算機科学の中枢になると信じる<ref>{{Cite web|title=STLport: An Interview with A. Stepanov|url=http://www.stlport.org/resources/StepanovUSA.html|website=www.stlport.org|accessdate=2021-09-26}}</ref>。
ジェネリックプログラミングは[[関数型言語]]に逆輸入されて、それと{{仮リンク|アドホック多相|en|Ad hoc polymorphism}}を融合した[[型クラス]]の機能が「[[Haskell]]」に登場している<ref>{{cite web|url=https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf|title=Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming|publisher=Microsoft|access-date=16 October 2016|author1=Lämmel, Ralf|author2=Peyton Jones, Simon}}</ref>。[[型クラス]]は[[モナド (プログラミング)|モナド]]の実践手段にもされ、それをヒントにした「[[Scala]]」はジェネリックプログラミングと{{仮リンク|サブタイプ多相|en|Subtyping polymorphism}}を融合した[[共変性と反変性 (計算機科学)|共変性と反変性]]の機能を導入している。また「[[D言語]]」は{{仮リンク|多段階メタプログラミング|en|Multi-stage programming}}を[[C++]]のテンプレートに融合した強力な[[テンプレートメタプログラミング|テンプレート]]機能を提供して、ジェネリックプログラミングの異なる発展形を示した。
== 特徴 ==
ジェネリックプログラミングの特徴は、型を抽象化してコードの再利用性を向上させつつ、[[静的型付け]]言語の持つ型安全性を維持できることである。
123 ⟶ 117行目:
[[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) を採るプログラム単位(パッケージまたは副プログラム)である。
===[[Ada]]のジェネリクス===
<!-- en の"one or more"は誤り.仮パラメータを持たない汎用体も宣言できる.-->
Adaには1977年および1980年の設計当初から汎用体 (generics) が存在する。標準ライブラリでも多くのサービスを実装するために汎用体を用いている。Ada2005では1998年に規格化されたC++の[[Standard Template Library]] (STL) の影響を受けた広範な汎用コンテナが標準ライブラリとして追加された。汎用体 (generic unit) とは、0または複数の汎用体仮パラメータ (generic formal parameters) を採るプログラム単位(パッケージまたは副プログラム)である。<!-- en の"one or more"は誤り.仮パラメータを持たない汎用体も宣言できる.-->
 
汎用体仮パラメータとしては、オブジェクト(変数・定数)、データ型、副プログラム、パッケージ,さらには他の汎用体のインスタンスさえ指定することができる。汎用体仮パラメータのデータ型としては、離散 (discrete) 型、[[浮動小数点数]]型、[[固定小数点数]]型、アクセス([[ポインタ (プログラミング)|ポインタ]])型などを用いることができる。
132 ⟶ 127行目:
汎用体をインスタンス化する際、プログラマは全ての仮パラメータに対応する実パラメータを指定する必要があるが、プログラマが明示的に全ての実パラメータを指定しなくても済むよう,仮パラメータにはデフォルトを指定することもできる。インスタンス化してしまえば,汎用体のインスタンスは、汎用体ではない通常のプログラム単位であるかのように振舞う。インスタンス化は実行時、例えばループの中などで行うことも可能である。
 
'''===Adaの例'''===
 
汎用体パッケージの仕様部
185 ⟶ 180行目:
-- この時点でDocument_Nameファイルを開いたり、読み込んだりが可能
end Edit;
</syntaxhighlight>'''利点と制限'''
 
===利点と制限===
Adaの言語構文では、汎用体仮パラメータとして何を許容するか、精密に制約条件を課することができる。例えば実パラメータとしてはモジュラー型(任意の上限で巡回する符号なし整数型)のみを許容するように、仮パラメータとして指定することも可能である。さらには汎用体仮パラメータ間に一定の制約があるように規制することも可能である。例えば、
 
211 ⟶ 207行目:
:ただし仮パラメータに精密な制約を課することができるため、例えば、スワップ副プログラムを仮パラメータとして、[[ソート]]を目的とした汎用体の挙動をスワップ対象に応じて変化させたり、離散型の規定演算である大小判定を用いてMaxを実装するなど、特化の利点とされる目的の一部は他の方法により、達成することができる。
 
==C++のテンプレート==
=== [[Eiffel]]のジェネリシティ ===
1986年公開のEiffelは、初回版からジェネリシティを採用しており、クラスに総称化を取り入れた最初のオブジェクト指向言語である。ジェネリッククラスの定義は以下のようになる。
class
LIST [G]
...
feature -- Access
item: G
-- The item currently pointed to by cursor
...
feature -- Element change
put (new_item: G)
-- Add `new_item' at the end of the list
...
ジェネリッククラスのインスタンス化は以下のようになる。
list_of_accounts: LIST [ACCOUNT]
-- Account list
list_of_deposits: LIST [DEPOSIT]
-- Deposit list
ジェネリッククラスの型パラメータは制約(constraint)で修飾できる。
class
SORTED_LIST [G -> COMPARABLE]
 
===[[C++]]のテンプレート===
{{main|テンプレート (プログラミング)}}
 
1990年から導入されたC++のテンプレート機能関数テンプレート、クラステンプレートをサポートするほか、[[C++14]]では変数テンプレートもサポートするようになった。C++のテンプレートは特に静的な[[ダック・タイピング]]を可能にする点で強力であり、JavaやC#のジェネリクスと比べて柔軟性が高い一方、テンプレート引数に関する制約条件を明示的にコード上で記述できないことからコンパイルエラーメッセージが難解になりやすい。テンプレートはC++言語仕様の複雑化の要因にもなっている。
 
C++の[[Standard Template Library]] (STL) はテンプレートによる汎用的なアルゴリズムとデータ構造を提供する。
 
===[[D言語]]のテンプレート===
2001年公開のD言語はC++のものを発展させたテンプレートをサポートする。大半のC++テンプレートの表現はD言語でもそのまま利用できる。それに加え、D言語は一部の一般的なケースを合理化する機能をいくつか追加する。
 
最もはっきりとした違いは一部のシンタックスの変更である。D言語はテンプレートの定義で山形カッコ<code>&lt; &gt;</code>の代わりに丸カッコ<code>( )</code>を使用する。またテンプレートのインスタンス化でも山形カッコの代わりに<code>!( )</code>構文(感嘆符を前に付けた丸カッコ)を使う。従って、D言語の<code>a!(b)</code>はC++の<code>a&lt;b&gt;</code>と等価である。この変更は、テンプレート構文の[[構文解析]]を容易にするためになされた(山形カッコは比較演算子との区別がつきにくく、構文解析器が複雑化しがちであった)。
 
'''===Static-if'''===
 
D言語はコンパイル時に条件をチェックする<code>static if</code>構文を提供する。これはC++の<code>#if</code>と<code>#endif</code>のプリプロセッサマクロに少し似ている。<code>static if</code>はテンプレート引数や、それらを使用したコンパイル時関数実行の結果を含めた全てのコンパイル時の値にアクセスできるというのがその主要な違いである。従ってC++でテンプレートの特殊化を必要とする多くの状況でも、D言語では特殊化の必要なく容易に書ける。D言語の再帰テンプレートは通常の実行時再帰とほぼ同じように書ける。これは典型的なコンパイル時の関数テンプレートに見られる。
 
257 ⟶ 229行目:
const Factorial = n * Factorial!(n - 1);
}
</syntaxhighlight>'''エイリアスパラメーター'''
 
===エイリアスパラメーター===
D言語のテンプレートはまたエイリアスパラメーターを受け入れることができる。エイリアスパラメーターはC++の<code>typedef</code>と似ているが、テンプレートパラメーターを置き換えることもできる。これは今後利用可能なC++0x仕様に追加されるであろう、C++のテンプレートのテンプレート引数にある機能の拡張版である。エイリアスパラメーターは、テンプレート、関数、型、その他のコンパイル時のシンボルを指定できる。これは例えばテンプレート関数の中に関数をプログラマーが''挿入''できるようにする。
 
280 ⟶ 253行目:
</syntaxhighlight>
 
=== [[Scala]]Javaのジェネリクス ===
2003年公開のScalaは、ジェネリックプログラミングとサブタイピングを融合した最初の言語であり、[[ミックスイン]]も融合している。それをサポートする[[共変性と反変性 (計算機科学)|共変性と反変性]]、上限型境界と下限型境界、関連型の機能を初めて導入している。ただし上限型境界は型制約(constraint)と同じものなのでこれは初導入ではない。
 
以下のコード例は、いわゆる連結リストの作成であり、リスト要素を共変性にして、appendメソッドの引数に反変性と下限型境界<code>>:</code>を用いている。<syntaxhighlight lang="scala">
trait Node[+B] {
def append[D >: B](elem: D): Node[D]
}
 
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def append[D >: B](elem: D): ListNode[D] = ListNode(elem, this)
def first: B = h
def second: Node[B] = t
}
 
case class Null[+B]() extends Node[B] {
def append[D >: B](elem: D): ListNode[D] = ListNode(elem, this)
}
</syntaxhighlight>
 
===[[Java]]のジェネリクス===
2004年、[[Java Platform, Standard Edition|J2SE]] 5.0の一部として[[Java]]にジェネリクスが追加された。C++のテンプレートとは違い、Javaコードのジェネリクスはジェネリッククラスの1つのコンパイルされたバージョンだけを生成する。ジェネリックJavaクラスは型パラメータとしてオブジェクト型だけを利用できる(基本型は許されない)。従って<code>{{Javadoc:SE|java/util|List}}&lt;{{Javadoc:SE|java/lang|Integer}}&gt;</code>は正しいのに対して<code>{{Javadoc:SE|java/util|List}}&lt;int&gt;</code>は正しくない。
 
308 ⟶ 262行目:
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}}&lt;?&gt;</code>は無名のオブジェクト型を持つリストを表す。引数として<code><nowiki>List<?></nowiki></code>を取るようなメソッドは任意の型のリストを取ることができる。リストからの読み出しは{{Javadoc:SE|java/lang|Object}}型のオブジェクトを返し、そしてnullではない要素をリストへ書き込むことはパラメーター型が任意ではないために許されない。
 
316 ⟶ 269行目:
ジェネリック要素の下限を指定するために、ジェネリック型が境界クラスのスーパークラスであることを示すキーワード<code>super</code>が使用される。そして<code>{{Javadoc:SE|java/util|List}}&lt;? super {{Javadoc:SE|java/lang|Number}}&gt;</code>は<code>{{Javadoc:SE|java/util|List}}&lt;{{Javadoc:SE|java/lang|Number}}&gt;</code>や<code>{{Javadoc:SE|java/util|List}}&lt;{{Javadoc:SE|java/lang|Object}}&gt;</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>&lt;?&gt;</code>以外に、型パラメーターの型でジェネリッククラスの配列を生成することが不可能であるということだ。これは言語の配列の取り扱い方法に起因するものであり、タイプセーフを維持するために、明示的にキャストしなくともコンパイラが警告を出さないことを全てのコードで保証する必要があるからである。
 
===[[C Sharp|C#]]Haskellのジェネリス=プログラミング==
[[Haskell]]言語にはパラメータ化された型 (parameterized types)、パラメータ多相 (parametric polymorphism)、そしてJavaのジェネリクスやC++のテンプレートの両方に似たプログラミングのスタイルをサポートする型クラス (type classes) がある。Haskellプログラムではこれらの構文を様々なところで利用しており、避けることはかなり難しい。Haskellはまた、さらなるジェネリック性と、多態が提供する以上の再利用性を目指すようにプログラマーと言語開発者を奮起させる、さらに独特なジェネリックプログラミングの機能がある。
2005年11月に、C#(およびその他の.NET言語)のジェネリクスは.NET Framework 2.0の一部として追加された。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++のテンプレート両方をサポートする。ただしこれらの間に互換性はない。
 
===[[Haskell]]の型システム===
1990年からの純粋関数型言語[[Haskell]]には、パラメータ化された型 (parameterized types)、パラメトリック多相 (parametric polymorphism) 、そしてJavaのジェネリクスやC++のテンプレートの両方に似たプログラミングのスタイルをサポートする[[型クラス]] (type classes) がある。Haskellプログラムではこれらの構文を様々なところで利用しており、避けることはかなり難しい。Haskellはまた、さらなるジェネリック性と、多態が提供する以上の再利用性を目指すようにプログラマーと言語開発者を奮起させる、さらに独特なジェネリックプログラミングの機能がある。
 
Haskellの6つの事前定義された型クラス(同一性を比較できる<code>Eq</code>という型と、値を文字列に変換できる<code>Show</code>という型を含む)は''導出インスタンス'' (derived instances) をサポートしている特別なプロパティを持つ。プログラマーが新しい型を定義するということは、クラスのインスタンスを宣言するときに、普通であれば必要なクラスメソッドの実装を提供することなく、この型がこれらの特別型クラスのインスタンスとなることを明示できるということである。全ての必要なメソッドは型の構造に基づいて導出(つまり自動的に生成)される。
371 ⟶ 288行目:
<code>Eq</code>と<code>Show</code>の導出インスタンスへのサポートは、それらのメソッドである<code>==</code>と<code>show</code>を、パラメーター的な多態関数とは質的に異なるジェネリックにする。これらの"関数"(より正確には型でインデックス付けられた (type-indexed) 関数のファミリー)はたくさんの異なる型の値を受け入れることができ、各引数の型によってそれらは異なる動作をするが、新しい型へのサポートを追加するためにわずかな作業が必要とされる。Ralf Hinze氏 (2004) は、あるプログラミングテクニックによりユーザー定義型のクラスに対して同様の結果を達成できることを示した。彼以外の多くの研究者はこれと、Haskellの流れとは違う種類のジェネリック性やHaskellの拡張(下記参照)に対する取り組みを提案していた。
 
=== PolyP ===
'''「決まり文句を捨てる」アプローチ'''
 
決まり文句を捨てるアプローチ (Scrap your boilerplate approach) は簡易的なジェネリックプログラミングのHaskellに対するアプローチである (Lämmel and Peyton Jones, 2003)。このアプローチはHaskellのGHC>=6.0の実装でサポートされる。このアプローチを使うことで、ジェネリックな読み込み、ジェネリックな明示、ジェネリックな比較(つまりgread、gshow、geq)と同様に、横断スキーム(例えばいつでもどこでも)のようなジェネリック関数をプログラマーは記述できる。このアプローチはタイプセーフなキャストとコンストラクタアプリケーションの実行のための一部の基本要素に基づいている。
 
==== PolyP ====
PolyPはHaskellに対する最初のジェネリックプログラミング言語拡張であった。PolyPではジェネリック関数は''polytypic''と呼ばれた。通常データ型のパターン[[ファンクタ]]の構造によって構造的な導出を通じて定義できるpolytypic関数のような特別な構文を言語に導入した。PolyPでの通常データ型はHaskellのデータ型のサブセットである。通常データ型tは''* → *''の種類でなければならず、もし''a''が定義における表面的な型の引数である場合は、''t''に対する全ての再帰呼び出しは''t a''形式でなければならない。これらの制約は、異なる形式の再帰呼び出しである入れ子のデータタイプと同様に、上位に種類付けされたデータ型を規定する。
 
397 ⟶ 310行目:
</pre>
 
====ジェネリックHaskell====
ジェネリックHaskellは[[ユトレヒト大学]]で開発されたHaskellのもう1つの拡張だ。この拡張は下記の特徴がある。
 
424 ⟶ 337行目:
</pre>
 
===「決まり文句を捨てる」アプローチ===
===その他===
 
[[関数型言語]]の数々は、パラメータ化された型 (parameterized types) とパラメトリック多相 (parametric polymorphism) の形で小規模なジェネリックプログラミングをサポートする。さらに「[[Standard ML]]」と「[[OCaml]]」は、クラステンプレートと[[Ada]]のジェネリックパッケージに似たファンクタを提供する。
決まり文句を捨てるアプローチ (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>
 
== 脚注 ==
433 ⟶ 385行目:
 
== 関連項目 ==
* [[総称型]]
* [[ポリモーフィズム]]
* [[ダック・プログラミイピング]]
* [[テンプレートメタプログラミング]]
*[[型システム]]
 
{{Normdaten}}{{プログラミング言語の関連項目}}
{{DEFAULTSORT:しえねりつくふろくらみんく}}
[[Category:ソフトウェア工学]]
[[Category:プログラミングパラダイム]]
[[Category:ポリモーフィズム (計算機科学)]]
[[Category:プログラミング言語の概念]]