Goにジェネリック(総称的)プログラミングのサポートを追加する新たなドラフトの中心となっているのは、型パラメータを型および関数に制約するために使用される、コントラクト(contract)の概念である。さらにドラフトでは、型推論を導入して、多くの場合において型を省略可能にすることで、ジェネリック型と関数の使用を簡略化する。
GoogleのエンジニアであるIan Lance Taylor氏と、Go言語の作者のひとりであるRobert Griesemer氏によるこの新たな提案は、Goの構文をパラメトリックなポリモーフィズムで拡張することが目的である。これにより、構造上の特性が同じであれば、実際の型とは独立して値を処理できる関数と型の記述が可能になる。Goではジェネリクス(generics)という用語を使用しているが、ドラフトの著者たちが警告するように、C++やC#、Java、Rustなどの言語におけるジェネリクスと混同してはならない。
次の簡単な例は、ジェネリックなReverse
関数をGoで定義し、呼び出す方法を示すものだ。
func Reverse (type Element) (s []Element) {
first := 0
last := len(s) — 1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
}
}
...
Reverse(int)(s)
上記のReverse
は、代入をサポートするジェネリックなElement
型の値のリストを処理する。関数の呼び出し時には、実際の引数と、リスト内の値の型の両方を指定するが、s
はすでにElement
型であることが分かっている場合が多いので、型を省略して、単にReverse(s)
と記述することが可能である。
上記の例のElement
のような型パラメータは、Goの他の型と同様に扱うことができる。前述のようにReverse
関数では、Element
型が代入をサポートすることのみを要求している。これはGoのすべての型に当てはまるので、コントラクトを指定する必要はない。Goでのコントラクトは、それに準拠する型をリストすることで指定する。例えば、Min
オペレーションを定義する場合には、標準ライブラリで定義されている"<
"演算子を提供するすべての型を対象にして、ジェネリックに定義することが可能だ。この関係は、次例のようなOrdered
コントラクトを通じて表現することができる。
contract Ordered(T) {
T int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr,
float32, float64,
string
}
これが定義できれば、ジェネリックなMin
関数を、次のように記述することが可能になる。
func Min (type T Ordered) (a, b T) T {
if a < b {
return a
}
return b
}
メソッドでのコントラクトの使用や、複数の型パラメータを使用するコントラクトなど、他に考慮が必要なケースはいくつかあるが、基本的な考え方はここに示したものと変わらない。それよりも興味深いのは、このことがGoにおいて、現在のジェネリック設計のドラフトを形成する元になった根拠を連想させる点だ。特にTaylor氏が言及しているのは、ユーザではなくジェネリックコードの作者が複雑性を担当するという、設計上の望ましい姿や、ジェネリックコードの作者とユーザが分離されることの価値、ビルド時間と実行時間の両方を短縮する必要性、そして最後に、言語の明確性と明快性の維持である。これらの特性は、すべてジェネリクスの現在のドラフトに取り入れられており、Goのジェネリクス提案によって保証されるべきものだ、とTaylor氏は言う。
今回説明したドラフトには、まだリファレンス実装が用意されていない。新しい言語機能を使用して実際に試す上で、これは重要な要件であり、Goのジェネリクスの進化における次のステップとなる。リファレンス実装が利用可能になれば、開発者がその提案を評価して、想定しているようなジェネリックなコードの記述が可能であることを確認できると同時に、フィードバックの提供も可能になるからだ。