ジェネリックとは?型をまたいで動くコード
ジェネリックとは型でパラメータ化されたコードです。一度書けば、完全な型安全性を保ちながら多くの型で動く関数やデータ構造です。
ジェネリックがなければ、型ごとに同じロジックを何度も書くか、何でも入る型を使って型安全性を捨てるかのどちらかです。ジェネリックは、型のプレースホルダを持つ関数やコンテナを書けるようにしてこれを解決し、それは使うときに埋められます。ジェネリックなリストは文字列でも数値でも保持でき、コンパイラは依然として各使用を正しく検査します。
ジェネリックが与えてくれるもの
ジェネリックは、重複や安全性の喪失なしに1つの定義が多くの型に仕える手助けをします。ジェネリックな List<T> はあなたが供給する任意のTのリストであり、コードが抽象的に書かれていても、コンパイラは数値のリストが数値のみを保持することを強制します。
ジェネリックが重要な理由
- 再利用: アルゴリズムやコンテナをすべての型に対して一度書く。
- 型安全性: castなし、誤用による実行時の型エラーなし。
- 明確な意図: シグネチャが型どうしの関係を表現する。
- より優れたツール: autocompleteと検査が具体的な型に追随する。
ジェネリック vs 代替手段
代替手段は、型ごとにコードを複製する(退屈でエラーを招く)か、不透明なany/object型を使う(検査を捨てる)ことです。ジェネリックは後者の再利用を、前者の安全性とともに与えます。
ジェネリックの実装方法
言語によって異なります。一部はコンパイル時に各具体型ごとにジェネリックコードを特殊化し(RustやC++のようなmonomorphization)、速いですがバイナリが大きくなります。他は型を消去して1つの実装を共有します(Javaのようなtype erasure)。トレードオフは速度か、コードサイズかです。
手短な例
ジェネリックな function first<T>(items: T[]): T は、任意の型Tの配列の最初の要素を返し、コンパイラは結果がTだと分かるため、型情報を端から端まで保ちます。
CIにおけるジェネリック
重いジェネリックコード、特にmonomorphizeされたジェネリックは、コンパイラが具体型ごとにバージョンを生成するため、コンパイルをより遅くメモリを多く消費させ得ます。より大きなrunner(Latchkey)はこれらのbuildを短縮し、コンパイル中の一時的なOOMやtoolchainの不調は自動でリトライできます。
重要なポイント
- ジェネリックは、型安全性を保ちながら多くの型で動く型パラメータ化されたコードである。
- ジェネリックは、型システムが提供する安全性を犠牲にせず再利用を与える。
- 実装戦略は、実行速度をバイナリサイズやbuild時間と引き換えにする。