ジェネリックプログラミング
出典: フリー百科事典『ウィキペディア(Wikipedia)』
修正依頼 | この項目は、文章が冗長・まとまりを欠く・エッセイ的である・不適当な翻訳など改善の余地があるため、修正・推敲を広く求めています。 |
ジェネリック(総称)プログラミング(generic programming)はデータ形式に依存しないコンピュータプログラミングの方式。これはデータ型でコードがインスタンス化されるかあるいはデータ型がパラメータとして渡されるかということに関わらず同じソースコードを利用できるということである[1]。ジェネリックプログラミングは言語によって異なった形で実装やサポートがなされている。ジェネリックプログラミングの機能は70年代にCLUやAdaのような言語で登場し、次にBETA、C++、D、Eiffel、Java、そして今はもうないDECのTrellils-Owl言語などを含む数多くのオブジェクトベース(object-based)やオブジェクト指向(object-oriented)言語に採用された。
1995年の書籍デザインパターンの共著者は(Ada、Eiffel、Java、C#の)ジェネリクスや(C++)のテンプレートとしても知られるパラメータ化された型(parameterized types)としてジェネリクスについて触れている。これらは、利用する全ての他の型を指定することなく、型が定義されるようにすることを可能にする(未指定の型は使用する時点で引数として与えられる)。このテクニック(特にデリゲーションを組み合わせるとき)は非常に強力であり、また「動的に、高度にパラメーター化されたソフトウェアはより静的なソフトウェアよりも理解しづらい」とその著者は忠告している(Gang of Four 1995:21)。
ジェネリックプログラミングの利点の一例として、オブジェクトのコンテナを作成する際に、データ形式が異なるだけで事実上コードは同一であったとしても、各データ形式毎に別の実装を書くのが一般的だ。一方、ジェネリックプログラミングを用いて宣言できる、テンプレートクラス(C++の用語を使えば)を定義することが可能だ。
template<typename T> class List { /* class contents */ }; List<Animal> list_of_animals; List<Car> list_of_cars;
上記のTはインスタンス化される型を表す。そしてこの定義によって生成される型は指定された型Tのリストとして扱われる。これらの「T型のコンテナ」は一般にジェネリクス(generics)と呼ばれ、ジェネリックプログラミングのテクニックの一つである。このテクニックは、サブタイプやシグネチャといった契約が維持される限り、異なるデータ型を含めるクラスの定義を可能にする(置き換え可能なサブクラスのアルゴリズムを使う多態と混同しないように)。上記はジェネリックプログラミングのもっとも一般的な例であり、一部の言語ではこの形式のみを実装するが、コンセプトとしてのジェネリックプログラミングはジェネリクスに限定されない。ジェネリックプログラミングのもう一つの応用例として、型に依存しない関数であるスワップの例を示す。
template<typename T> void Swap(T & a, T & b) //"&"はリファレンスとしてパラメーターを渡す { T temp = b; b = a; a = temp; } string hello = "world!", world = "Hello, "; Swap( world, hello ); cout << hello << world << endl; //出力は"Hello, world!"
上記の例で使用されたC++のtemplate
構文は、プログラマーと言語開発者たちの間にこの概念を普及させたジェネリックプログラミングの構文としてよく引き合いに出される。この構文はジェネリックプログラミングの表現の全てをフルサポートする。またD言語はC++のテンプレートをベースに構文を単純化した完全なジェネリックの機能を提供する。JavaはJ2SE 5.0の導入からC++の文法に似せたジェネリックプログラミングの機能を提供しており、ジェネリックス(「T型のコンテナ」)という、ジェネリックプログラミングのサブセットを実装する。
C# 2.0、Chrome 1.5、Visual Basic .NET 2005は、Microsoft .NET Framework 2.0からサポートされたジェネリクスを利用するための構文をもつ。プログラミング言語のMLファミリーはパラメトリックな多態(parametric polymorphism)とファンクタと呼ばれるジェネリックモジュールを利用してのジェネリックプログラミングを推奨する。Haskellのタイプクラスのメカニズムもまたジェネリックプログラミングをサポートする。
Objective-Cにあるような動的型付けと(必要であれば)プロトコルを注意深く使用すると、ジェネリックプログラミングのテクニックを使う必要がなくなる。なぜなら、どんなオブジェクトでも含むことのできる汎用型が存在するためである。Javaもまたそうであるが、キャストが必要なので静的な型付けの統一性を乱してしまう。それに対し、ジェネリクスは静的な型付けについての利点を持ちながら動的な型付けの利点をいくらか得られる唯一の方法である。
目次 |
[編集] Adaのジェネリクス
1977年~1980年、Adaは設計の当初からジェネリクス(generics・汎用体)を持っていた。標準ライブラリは多くのサービスを提供するためにジェネリクスを使う。Ada2005はC++のStandard Template Library(STL)にインスパイアされた広範囲なジェネリクスコンテナライブラリを標準ライブラリに追加している。
ジェネリックユニット(generic unit)は1つまたは複数のジェネリック形式のパラメーター(generic formal parameters)を取るパッケージまたはサブプログラムだ。
ジェネリック形式のパラメーター(generic formal parameter)は、値や変数、定数、型、サブプログラム、そして他のインスタンスすらも指定されるジェネリックユニット(訳注・この訳は要再検討)。ジェネリック形式のタイプでは、シンタックスは分離型(dicscrete)、浮動小数点数、固定小数点数、アクセスタイプ(ポインタ)などを区別する。一部の形式パラメーターはデフォルト値を持てる。
ジェネリックユニットをインスタンス化するため、プログラマーは実際のパラメーターをそれぞれの形式に渡す。ジェネリックのインスタンスはそしてその他のユニットであるかのように動作する。ジェネリックユニットはたとえばループの中などで実行時にインスタンス化することができる。
[編集] 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;
ジェネリックパッケージのインスタンス化
type Bookmark_Type is new Natural; -- 編集中のテキストドキュメント内の場所を記録する package Bookmark_Stacks is new Stacks (Max_Size => 20, Element_Type => Bookmark_Type); -- ドキュメント中の記録された場所にユーザーがジャンプできるようにする
ジェネリックパッケージのインスタンスの利用
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;
[編集] 利点と制限
言語構文はジェネリック形式パラメーター上で非常に正確な規制の仕様ができる。例えばジェネリック形式型が実際のモジュール型のみを受け付けるように指定することが可能だ。またジェネリック形式パラメーターの間の規制を表現することも可能であり、例えば、
generic type Index_Type is (<>); -- 分離型(discrete type)のはず type Element_Type is private; -- 任意の型 type Array_Type is array (Index_Type range <>) of Element_Type;
この例では、Array_TypeはIndex_TypeとElement_Typeの両方により規制される。ユニットをインスタンス化する際にはプログラマーはこれらの規制を満たす実際の配列型を渡さなければならない。
このきめ細かい制御の問題は複雑な構文であるが、全てのジェネリック形式パラメーターは仕様で完全に定義されるため、コンパイラはジェネリックの本体を確認することなくジェネリクスをインスタンス化できる。
C++と違いAdaはジェネリックインスタンスの(特定の型への)特化を許さず、全てのジェネリクスが明示的にインスタンス化されることを要求する。これらのルールはいくつかの結果をもたらす。
- コンパイラは共有ジェネリクス(shared generics)を実装できる。ジェネリックユニットのオブジェクトコードは全てのインスタンスから共有できる(もちろんプログラマーがサブプログラムのインライン化を要求しない限り)。さらなる結果として、
- コードが肥大化する可能性がない(コードの肥大化はC++では一般的であり下記の説明にあるように特別な配慮が求められる)。
- 新しいインスタンス(を作るため)に新しいオブジェクトが求められないために、コンパイル時と同様に、実行時にジェネリクスをインスタンス化することができる。
- ジェネリック形式オブジェクトに対応する実際のオブジェクトはジェネリックの中で常に非スタティックであるものと想定される。詳細と結論についてはWikibookのGeneric formal objectsを参照。
- ジェネリクスの全てのインスタンスは正確に同じ状態であり、レビューや他の人が書いたプログラムを理解することは簡単だ。考慮すべき「特別な場合」がない。
- 全てのインスタンス化は明示的であり、プログラムの理解を難しくするであろう暗黙のインスタンス化は無い。
- Adaでは特化ができないのでテンプレートメタプログラミングができない。
[編集] C++のテンプレート
テンプレートは特に多重継承と演算子のオーバーロードを組み合わせた場合に、C++のプログラマにとって強力なツールだ。C++のStandard Template Library(STL)はテンプレートに関連するフレームワークを提供する。
C++のテンプレートはまた、実行時ではなくコンパイル時にコードの一部を事前評価する方法であるテンプレートメタプログラミングにも利用できる。
[編集] 技術概要
関数テンプレートとクラステンプレートの2種類のテンプレートがある。関数テンプレートは普通の関数のように動作するが、型に依存することなく多様な引数を受け入れられる。例えば、C++ STLには、xとyのどちらか大きいほうを返すmax(x, y)
の関数テンプレートがある。max()
はこのように実装できるだろう。
template <typename T> T max(T x, T y) { if (x < y) return y; else return x; }
このテンプレートは関数のように呼び出せる。
cout << max(3, 7); // 7を出力
コンパイラは引数を評価してこれが max(int, int)
の呼び出しであることを決定し、型T
がint
である関数のバージョンをインスタンス化する。
引数x
とy
が整数や文字列、あるいは"x < y
"と表現することに意味のあるそれ以外の型(正確に言えばoperator<
を指定するあらゆる型)のとき、これは機能する。これは利用可能な型のセットに何らかの共通の継承がある必要がなく、これは実際には静的なダックタイピング(duck typing)である。もしプログラムがカスタムデータ型を定義するならば、max()
で利用可能にするために必要なことはその型の<
の意味を定義するために演算子をオーバーロードするということだけである。この孤立した例では小さな利点にしか見えないかもしれないが、STLのような総合ライブラリの中では、わずかなオペレーターを定義するだけで新しいデータ型に対して広い機能性をプログラマーが得ることができる。単純に<
を定義することだけで、標準のsort()
や stable_sort()
、binary_search()
といったアルゴリズムを型に利用でき、またset
やヒープや連想配列等々の内部データ構造に利用できる。
C++のテンプレートは完全にコンパイル時点でタイプセーフである。例えば、複素数には厳密な順序がないので標準形式のcomplex
は<
演算子を定義しない。従ってmax(x, y)
関数にxとyとしてcomplex
型を指定した場合、コンパイルエラーとなる。同様に、<
をあてにする他のテンプレートはcomplex
データに適用できない。残念ながらコンパイラは歴史的にやや難解で役に立たないエラーメッセージをこの種のエラーに対して生成する。ある特有のオブジェクトがメソッドプロトコルに固執するのを保障することはこの問題を軽減できるかもしれない。
2つ目のテンプレートの種類であるクラステンプレートは同じコンセプトをクラスに拡張する。クラステンプレートはよくジェネリックコンテナを作るのに利用される。例えば、STLには連結リストコンテナがある。整数の連結リストを作るためにはlist<int>
と書く。文字列のリストはlist<string>
である。list
は例えどんな型が<
と>
の間に指定されても動作する共通の関数セットを持っている。
[編集] テンプレートの特殊化
テンプレートの特殊化(template specialization)はC++のテンプレートの強力な機能だ。インスタンス化されるであろうパラメーター化された型(parameterized type)に特化した特徴を備えた新しい実装ができる。テンプレートの特殊化は、特定の形式に最適化できるようにすることと、コード肥大化の削減に役立てるという2つの目的がある。
例としてsort()
テンプレート関数について考える。このような関数の第一の動作はコンテナのポジションの2つの値を入れ替えまたは交換することだ。 値が多い場合(各要素を格納するためにメモリを消費するという点で)、オブジェクトへのポインタのリストを先に構築し、それらのポインタをソートして、最終的なソートされたシーケンスを構築するのが多くの場合で高速だ。値が非常に少なければ単純に必要に応じて値をスワップで置き換えるのが普通は最も早い。しかしさらにパラメーター化された型がポインター型である場合はポインタの配列を構築する必要がない。テンプレートの特殊化はテンプレートの作者に複数の異なる実装を記述できるようにし、パラメーター化された型が各実装で使われなければならない特徴を指定できるようにする。
[編集] 長所と短所
max()
関数のようなテンプレートのユーザーの一部は(古いC言語の)関数形式のプリプロセッサマクロで満たされていた。例えば次のようなmax()
マクロがある。
#define max(a,b) ((a) < (b) ? (b) : (a))
これら2つのマクロとテンプレートはコンパイル時間を長くする。マクロは常にインラインとして展開される。テンプレートもまたコンパイラが適切と判断したときにインライン関数として展開される。従って2つの関数形式のマクロと関数テンプレートは実行時のオーバーヘッドがない。
しかしテンプレートは次のような目的でマクロより重要であると一般的に考えられている。テンプレートはタイプセーフである。テンプレートは関数形式のマクロを濫用したコードにある一般的なエラーの一部を回避する。恐らく最も重要なのは、テンプレートはマクロよりも大きな問題に対して効果があるようにするために設計されたということだ。
テンプレートを使用するのに3つの主要な欠点がある。コンパイラのサポート、貧弱なエラーメッセージ、コードの肥大化だ。
歴史的に多くのコンパイラがテンプレートについて非常に貧弱なサポートしかなく、そのためテンプレートを使うことで移植性を損なった。C++コンパイラがC++について考慮していないリンカーと共に利用されるとき、あるいは共有ライブラリの境界を越えてテンプレートを使おうとしたとき、サポートはまた貧弱となった。最新のコンパイラは今ではかなりしっかりしており、標準テンプレートをサポートし、新しいC++の標準であるC++ 0xはこれらの問題をさらに解決していることが期待される。
ほとんど全てのコンパイラは、テンプレートを使ったコードにエラーを検出したときに、混乱した、長い、そして役に立たないエラーメッセージを出力する。これはテンプレートを使った開発を難しくしている。
テンプレートの使用はコンパイラが余分なコード(テンプレートのインスタンス化)を生成する原因となりうるので、見境のないテンプレートの利用は過剰に大きな実行ファイルを出力してしまう。しかしながら一部のケースでは、賢明なテンプレート特殊化の使用はそのようなコード肥大化を劇的に減らすことができる。また、テンプレートによって生じる余分なインスタンス化は、デバッガが素直にテンプレートを扱うことを困難にする原因ともなりうる。例えば、ソースコードのテンプレートの中にデバッグのブレークポイントをセットする場合、実際のインスタンスの中の望ましい場所にセットすることに失敗するかも知れないし、そのテンプレートによってインスタンス化された全ての場所にブレークポイントをセットしなければならないかも知れない。
[編集] D言語のテンプレート
D言語はC++のものを発展させたテンプレートをサポートする。ほとんどのC++テンプレートの表現は、D言語でもそのまま利用できる。それに加え、D言語は一部の一般的なケースを合理化する機能をいくつか追加する。
最もはっきりとした違いは一部のシンタックスの変更だ。D言語はテンプレートの定義で山形カッコ< >
の代わりに丸カッコ( )
を使用する。またテンプレートのインスタンス化でも山形カッコの代わりに!( )
構文(感嘆符を前に付けた丸カッコ)を使う。従って、D言語のa!(b)
はC++のa<b>
と等価である。この変更は、テンプレート構文の構文解析を容易にするためになされた(山形カッコは比較演算子との区別がつきにくく、構文解析器が複雑化しがちであった)。
[編集] Static-if
D言語はコンパイル時に条件をチェックするstatic if
構文を提供する。これはC++の#if
と#endif
のプリプロセッサマクロに少し似ている。static if
はテンプレート引数を含めた全てのコンパイル時の値にアクセスできるというのがその主要な違いである。従ってC++でテンプレートの特殊化を必要とする多くの状況でも、D言語では特殊化の必要なく容易に書ける。D言語の再帰テンプレートは通常の実行時再帰とほぼ同じように書ける。これは典型的なコンパイル時の関数テンプレートに見られる。
template factorial(int n) { static if (n == 1) const int factorial = 1; else const int factorial = n * factorial!(n-1); }
[編集] エイリアスパラメーター
D言語のテンプレートはまたエイリアスパラメーターを受け入れることができる。エイリアスパラメーターはC++のtypedef
と似ているが、テンプレートパラメーターを置き換えることもできる。これは今後利用可能なC++0x仕様に追加されるであろう、C++のテンプレートのテンプレート引数にある機能の拡張版である。エイリアスパラメーターは、テンプレート、関数、型、その他のコンパイル時のシンボルでありえる。これは例えばテンプレート関数の中に関数をプログラマーが挿入できるようにする。
template wrapper(alias Fn) { // "extern(C)"インターフェイスでD言語の関数をラップする extern(C) void wrapper() { Fn(); } }
このテンプレートの種類はC言語APIとD言語のコードを接続するときに使いやすいだろう。仮想のC言語APIが関数ポインタを要求する場合、このようにテンプレートを利用できる。
void foo() { // ... } some_c_function(&wrapper!(foo));
[編集] Javaのジェネリクス
2004年、J2SE5.0の一部としてJava言語にジェネリクスが追加された。C++のテンプレートとは違い、Javaコードのジェネリクスはジェネリッククラスの1つのコンパイルされたバージョンだけを生成する。ジェネリックJavaクラスは型パラメーターとしてオブジェクト型だけを利用できる(基本型は許されない)。従ってList<Integer>
は正しいのに対してList<int>
は正しくない。
Javaではジェネリクスはコンパイル時に型の正しさをチェックする。そしてジェネリック型情報は型削除(type erasure)と呼ばれるプロセスを通じて除去され、ジェネリック型情報は親クラスのためにのみ保持される。例えば、List<Integer>
は任意のオブジェクトを保有できる非ジェネリックの(生の)List
に変換されるだろう。しかしながら、コンパイル時のチェックにより、コードが未チェックのコンパイルエラーを生成しない限り、型が正しいように結果のコードが保証される。
このプロセスの副作用は典型的にジェネリック型の情報を実行時に参照できないことだ。従って、実行時には、List<Integer>
とList<String>
が同じList
クラスであることを示す。この副作用を緩和するひとつの方法は、Collectionの宣言を修飾するJavaのCollections.checkedSet()
メソッドを利用して、実行時に型付けされたCollectionの不正利用(例えば不適切な型の挿入)をチェックすることによるものである。これは旧式のコードとジェネリクスを利用するコードを共存運用したい場合の状況で役立つ。
C++やC#のように、Javaはネストされたジェネリック型ができる。従ってList<Map<Integer, String>>
は有効な型だ。
[編集] ワイルドカード
Javaのジェネリック型パラメーターは特定のクラスに制限されない。与えられたジェネリックオブジェクトが持っているかもしれないパラメーターの型の境界を指定するためにJavaではワイルドカードを使用できる。例えば、List<?>
は無名のオブジェクト型を持つリストを表す。引数としてlistを取るようなメソッドは任意のタイプのリストを取ることができる。リストから読み取ることはObject
型のオブジェクトを返し、そしてnullではない要素をリストへ書き込むことはパラメーター型が任意ではないために許されない。
ジェネリック要素の上限を指定するために、ジェネリックタイプが境界クラスのサブクラス(クラスの拡張とインターフェイスの実装のいずれか)であることを示すキーワードextends
が使用される。そしてList<? extends Number>
は与えられたリストがNumber
クラスを拡張するオブジェクトを入れることを意味する。従って、リストが何の要素の型を保持しているのかがわからないために非null要素の書き込みが再度許されないのに対し、リストから要素を読むとNumberが返るだろう。
ジェネリック要素の下限を指定するために、ジェネリック型が境界クラスの派生クラスであることを示すキーワードsuper
が使用される。そしてList<? super Number>
はList<Number>
やList<Object>
でありえる。リストに正しい型を保存することが保障されるため任意のNumber
型の要素をリストに追加できるのに対し、リストから読み出しではObject
型のオブジェクトを返す。
[編集] 制約
Javaのジェネリクスの実装の制約により、配列のコンポーネントの型が何であるべきかを特定する方法がないために、ジェネリック型の配列を作成することは不可能だ。従ってnew T[size];
経由のようにメソッドが型引数Tを持っていた場合はプログラマーはその型の新しい配列を生成することが出来ない。(この制約はJavaのリフレクションのメカニズムを利用して回避することが可能だ。クラスT
のインスタンスが利用可能な場合、T
に対応するClass
オブジェクトのオブジェクトから1つを得て、新しい配列を生成するためにjava.lang.reflect.Array.newInstance
を使うことができる。) もう1つのJavaのジェネリクスの実装の制約は、 <?>
以外に、型パラメーターの型でジェネリッククラスの配列を生成することが不可能であるということだ。これは言語で配列が取り扱われる方法に起因するものであり、タイプセーフであるために明示的なキャストを使用することなく全てのコードがコンパイルの警告を引き起こさないことを保障する必要があるからである。
[編集] Haskellのジェネリックプログラミング
Haskell言語にはパラメータ化された型(parameterized types)、パラメータ的多態(parametric polymorphism)、そしてJavaのジェネリクスやC++のテンプレートの両方に似たプログラミングのスタイルをサポートする型クラス(type classes)がある。Haskellプログラムではこれらの構文を様々なところで利用しており、避けるようにすることはかなり難しい。Haskellはまた、さらなるジェネリック性と、多態が提供する以上の再利用性を目指すようにプログラマーと言語開発者を奮起させる、さらに独特なジェネリックプログラミングの機能がある。
Haskellの6つの事前定義された型クラス(同一性を比較できるEq
という型と、値を文字列に変換できるShow
という型を含む)は導出インスタンス(derived instances)をサポートしている特別なプロパティを持つ。プログラマーが新しい型を定義するということは、クラスのインスタンスを宣言するときに、普通であれば必要なクラスメソッドの実装を提供することなく、この型がこれらの特別型クラスのインスタンスとなることを明示できるということである。全ての必要なメソッドは型の構造に基づいて導出(つまり自動的に生成)される。
例として、下記の2分木型の宣言はこれがEq
とShow
のクラスのインスタンスになることを示している。
data BinTree a = Leaf a | Node (BinTree a) a (Bintree a) deriving (Eq, Show)
T
がそれらの演算子を自分でサポートしているという条件で、任意の型のBinTree T
形式のために比較関数(==
)と文字列表現関数(show
)が自動的に定義される結果となる。
Eq
とShow
の導出インスタンスへのサポートは、それらのメソッドである==
とshow
を、パラメーター的な多態関数とは質的に異なるジェネリックにする。これらの"関数"(より正確には型でインデックス付けられた(type-indexed)関数のファミリー)はたくさんの異なる型の値を受け入れることができ、各引数の型によってそれらは異なる動作をするが、新しい型へのサポートを追加するためにわずかな作業だけが必要とされる。Ralf Hinze氏(2004)は、あるプログラミングテクニックによりユーザー定義型のクラスに対して同様の結果を達成できることを示した。彼以外の多くの研究者はこれと、Haskellの流れとは違う種類のジェネリック性やHaskellの拡張(下記参照)に対する取り組みを提案していた。
[編集] PolyP
PolyPはHaskellに対する最初のジェネリックプログラミング言語拡張であった。PolyPではジェネリック関数はpolytypicと呼ばれた。通常データ型のパターンファンクタの構造によって構造的な導出を通じて定義できるpolytypic関数のような特別な構文を言語に導入した。PolyPでの通常データ型はHaskellのデータ型のサブセットである。通常データ型tは* → *の種類でなければならず、もしaが定義における表面的な型の引数である場合は、tに対する全ての再起呼び出しはt a形式でなければならない。これらの制約は、異なる形式の再起呼び出しである入れ子のデータタイプと同様に、上位に種類付けされたデータ型を規定する。
PolyPの展開された関数はここに例として示される。
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
[編集] ジェネリックHaskell
ジェネリックHaskellはUtrechit(ユトレヒト)大学で開発された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の比較関数の一例として。
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 |} = (==)
[編集] 「決まり文句を捨てる」アプローチ
決まり文句を捨てるアプローチ(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のジェネリクスは、コンパイラによるジェネリクス型から非ジェネリクス型へのコンバートとしてではなく、実行時に実装される。この設計の選択は追加機能を提供する作用がある[2]。
.NETジェネリクスの機能
- 型情報を削除せず、CLRの内部でジェネリクスが構築されるため(そしてコンパイラ上では全く構築しないため)、キャストや動的チェックの実行からくるパフォーマンスヒットがない。しかもプログラマーはリフレクションを通じてジェネリック情報にアクセスできる。
- 型情報を削除しないことによりJavaでは不可能なジェネリック型の配列の生成が可能だ。
- .NETはジェネリック型の引数として基本型とユーザー定義型の値を利用できる。その場合、JITは全ての特殊化のためにネイティブコードの新しいインスタンスを作成する。これはボクシング(boxing)を防止することでパフォーマンスを改善する。
- Javaのように、.NETはジェネリック型引数がそれら自信のジェネリック型であるようにできる。つまり、
List<List<Dictionary<int, int>>> x = new List<List<Dictionary<int, int>>>();
のようなコードは有効だ。 - C#(および一般の.NET)は、キーワード
where
[3]を使用することで、値型、クラス、コンストラクタの所有、インターフェイスからの派生などであるようにジェネリック型を制約することを含めた広範囲に渡るジェネリック型の制約が可能だ。型の制約はC#のジェネリクスでC++のテンプレートのような暗黙の型変換ができるようにする。型の制約によりperson.GetFirstName
のようなコードを書ける[4]。
interface IPerson { string GetFirstName(); string GetLastName(); } class Speaker { public void speakTo<T>(T person) where T : IPerson { string name = person.GetFirstName(); this.say("Hello, " + name); } }
T
の型の制約なしに、person
はオブジェクト型から派生したのメンバだけを持てる。Javaはジェネリック型を示すextends
キーワードを利用することにより類似のメカニズムをサポートする[5]。
[編集] その他の言語のジェネリックプログラミング機能
数多くの関数型言語はパラメータ化された型(parameterized types)とパラメータ化された多態(parametric polymorphism)の形で小規模なジェネリックプログラミングをサポートする。さらに標準MLとOCamlはクラステンプレートとAdaのジェネリックパッケージに似たファンクタを提供する。
[編集] 関連項目
カテゴリ: 修正依頼 | プログラム開発手法 | プログラミングパラダイム