Go開発者が最も多く望んできた機能のひとつであるジェネリクスが、数年間にわたって進化を続けたドラフト設計に基づいて言語に導入されることになり、言語変更提案プロセスが開始された。
現在の提案は、型コントラクト(type contract)を活用した最初の設計に基いており、パラメトリックなポリモーフィズムをGoに導入する。公開時にInfoQでも伝えているこの設計は、最終的には型パラメータ(type parameter)の概念を中心とした新しいものに取って代わられることになる。いずれの設計も、GoogleのエンジニアであるLance Taylor氏と、Goの開発者のひとりであるRobert Griesemer氏の手によるものであることから、両案の共通性については説明がつく。ただし現在のドラフトは、Goコミュニティ全体や、Go generics playgroundで経験したすべての開発者から多数の追加情報を受け入れたものだ、とTaylor氏は述べている。
新たな設計では、型パラメータは単なる抽象型であり、コンパイル時に型引数を通じて指定される具象型にマテリアライズされる。型パラメータは通常の非型パラメータと多少似ているが、独特の角括弧付きリストで指定されることが後者とは異なる。さらに型パラメータは、型と一緒に使用することもできる。
次の例は、ジェネリックな型AGenericType
の定義と、入力されたスライスの各要素をAGenericType
の値でラップした結果をスライスとして返す、ジェネリックな関数を示すものだ。
type AGenericType[T any] struct {
val T
}
func AGenericFunction[T any](s []T) []AGenericType[T] {
var result []AGenericType[T]
for _, x := range s {
result = append(result, AGenericType[T]{x})
}
return result
}
AGenericFunction[string]([]string{"A", "B"})
AGenericFunction([]int{1, 2}) // using type inference
他の言語でジェネリクスに慣れていれば、この構文を理解するのは難しくはないはずだ。
他の言語と同じように、型パラメータに特定の制約(constraint)を満足するように求めることができる。上の例では、型パラメータT
がany
制約を満足するように記述されている。これは、抽象型T
のパラメータとして任意の具象型を渡すことができる、という意味だ。型パラメータT
の引数として関数に渡す型を制限したい場合は、その型に関する制約を指定すればよい。制約とは単に、その型が実装しなければならないインターフェースである。これは、先程のジェネリック関数の実装で、そのインターフェースで実行可能なオペレーションのみを使うことができる、という意味を言外に含んでいる。次の例では、AGenericFunction2
の型を、SpecificInterface
を実装したものに制限する。
type SpecificInterface interface {
AvailableMethod() string
}
func AGenericFunction2[T SpecificInterface](s T) {
//-- can use s.AvailableMethod()
}
制約がまったくないという意味のany
制約は、type any interface{}
として定義されており、interface{}
と等価である。Goチームがinterface{}
をそのまま使わず、この新たなキーワードを導入することにしたのは、すべての型実装が定義として実装するGoの空インターフェースを使うことで、そのセマンティクスがより厳密なものになる、という意図からだ。一方で、制約のないパラメータ型を示すany
を不要にしてしまうと、構文解析が複雑なものになる。any
型は、現時点ではジェネリック関数と型のコンテキストでのみ使用可能だが、将来的にはもっとグローバルに使用できるようになるかも知れない、とGriesemer氏は述べている。
ここで重要なのは、提案中の変更がすべて後方互換性を備えていることだ。従って既存のGoプログラムは、ジェネリクスがコンパイラに導入された以降も引き続きコンパイルすることができる。
Goジェネリクスのドラフトには、ここで紹介したもの以外にも、理解しておきたいと思われるものが多数ある。複数のパラメタ型のサポート、自己参照型制約、型パラメータと型リストの使用、パラメータおよび制約としての型インターフェース、未解決の問題などだ。
Goにジェネリクスを導入する場合に懸念されることのひとつは、言語がこれまでより複雑になるのではないか、という点だ。しかしRobert Griesemer氏によれば、それよりも心配なのは、ジェネリクスがあらゆる場所で使用されることのないように、正しく使用することだという。
ジェネリクスの適切な使用について、ベストプラクティスを確立する必要があります。ジェネリクスには適所がありますし、まさにそれが必要な場合もあります。しかしながらジェネリクスは、Goのツールセットの新たなツールのひとつに過ぎません。すべてをジェネリクス形式で記述する、というのは誤った考え方です。
Goの変更プロセスが正式に始まったことで、ジェネリクスのドラフトは現在、Goコミュニティによってオープンに議論されている。関心のあるパーティならば何であれ、このプロセスに参加して批評や提案を行うことができる。ただし、現在のドラフトが定義されるまでに、相当量の検討や議論がすでに行われていることを忘れてはならない。従って、Goジェネリクスのこれまでの開発過程をフォローしていないのであれば、プロセスに参加する前に、ある程度のキャッチアップをしておく必要はあるだろう。