Go2 ではジェネリクスが入りそうという話
来たる Go2 ではもしかしたら Generics が入りそうだということを知ったのでその話。
Generics が入る?
2018年8月には Go2 の Generics についての草案が結構詳細に出ていたみたいです。
全然知らなかった。
この Overview は Go2 の Draft Design の一部で、
どうもこれをベースに今は議論が進んでいるっぽい? Golang のコミュニティに詳しくないので、この辺の話がどこで議論がなされているのかとか、いつぐらいに決まりそうなのかとかを全然知らないんですが、誰か詳しい人教えてほしい。
スターティング Go 言語を読んだ人はわかると思うんですけど、僕は Golang に対して構文解析を高速に保つために、かなり文法が工夫されているイメージを持っていたので、この思想がある限り、どれだけネットで「 Golang は Generics がないからクソ」って叩かれてても言語仕様に入ることはないかなぁと思っていたんですが、どうもそうでもなさそう?
ここからはここに載っていた話とその補足です。
なんのために Generics を入れるの?
Generics を採用する目的は、Golang に型パラメータを持つパラメトリックポリモーフィズムを導入することによって、不要な型の詳細を抽象化したライブラリを書けるようにすることです。
例えば、 sort パッケージは「不要な型の詳細」を書かされている最たる例で、 sort.Ints(), sort.Strings(), sort.Float64() のように型ごとにソート関数が用意されています。 Generics が導入されればこれらの関数が全て1つにまとめられるようになります。
type parameter
他のジェネリクスを持つ多くの言語と同じように、 type parameter を取ることでジェネリクスを実現する方針のようです。
type List(type T) []T
func Keys(type K, V)(m map[K]V) []K
C++ でいう <> の代わりに () を使うイメージですね。
type parameter を使って定義した関数は Generalized function と呼ばれ、通常の関数とは区別されます。
var ints List(int)
keys := Keys(int, string)(map[int]string{1:"one", 2: "two"})
Generalized function の呼び出しは、型名をパラメータとして与えることで呼び出します。
contract
ここまではまあ普通で、ここからがちょっと独自っぽい文法になります。
実際に Generalized function を使ったライブラリを整備していくことを考えたとき、全ての型で特殊化できてしまうとまずい場合があります(というか大概まずい)
そういうときは型に制約を設けることで、 type parameter に指定できる型を制限できます。
例えば抽象データ型の set を例に取ってみると、 set の要素は set に含まれる任意の要素同士で等価演算が行える必要があります。
contract Equal(t T) {
t == t
}
type Set(type T Equal) []T
「等価演算子が使える型のスライス」は新たな予約語 contract を使ってこのように表現します。
func (s Set(T)) Find(x T) int {
for i, v := range s {
if v == x {
return i
}
}
return -1
}
あとはこんな感じでメソッドも定義できます。
別の例として、 slice の合計を求める Generalized function の Sum は次のように定義できます。
func Addable(t T) {
t + t
}
func Sum(type T Addable)(x []T) T {
var total T
for _, v := range x {
total += v
}
return total
}
呼び出し
Generalized function は、 type argument を使って呼び出され Specialized function を返します。 Specialized function はその名の通り特殊化された関数で、通常の関数を同様に value argument を使って呼び出すことができます。
つまり、 Sum の呼び出しはこのようになります。
var x []int
total := Sum(int)(x)
また、 Generalized function と Specialized function の呼び出しは2つに分割することもできます。
var x []int
intSum := Sum(int) // intSumの型はfunc([]int) intになります
total := intSum(x)
必要な type argument が value argument から推論できる場合は、 type argument を省略することもできます。
var x []int
total := Sum(x) // Sum(int)(x)と同様の結果が得られます
複数の type parameter
type, func, contract は複数の type parameter をとることもできます。
contract Graph(n Node, e Edge) {
var edges []Edge = n.Edges()
var nodes []Node = e.Nodes()
}
func ShortestPath(type N, E Graph)(src, dst N) []E
contract の文法については、別の Draft Design があるので詳しくはそちらを参照してください。
未解決の問題
この Draft Design はコミュニティでの議論の出発点として用意されているだけのものなので、いくつかの問題点を抱えています。
Implied constraints
直訳すると暗黙の制約となります。
上に挙げたサンプルの1つに任意の key と value の型の map を使うものがありました。
func Keys(type K, V)(m map[K]V) []K
このとき、 map の key の型である K は全ての型を使えるわけではないので、この関数は正確には、 contract の別のサンプルで使った Equal を使って、次のように書くべきです。
func Keys(type K, V Equal(K))(m map[K]V) []K
K に関してそこまでの正確さをユーザに求めるのか、 map[K]V から推論されるのかは決められていません。
Dual implementation
直訳すると二重実装となります。
ここは僕の英語力が追いつかなくて、いまいち理解できなかったので、
の Discussion and Open Questions > Dual implementation を参照してください。
Contract bodies
直訳すると契約本体?かな?
ここも僕には意味が分からなかったので、
の Discussion and Open Questions > Contract bodies を参照してください。
まとめ
繰り返しになるんですが、こういう議論がどこでなされていて、いつ最終決定がなされそうなのか、 Golang のコミュニティについて詳しい人誰か教えてください。
元記事中には詳しくはこっちをみてねみたいなリンクがいっぱい貼ってあるんですけど、全部見るのは大変で追いきれなかったです。まあどうせこれから変わる内容なんで概要だけわかればええやろ多分。
ここに書いている内容はただの草案の状態なので、 Go2 が正式にリリースされたときにはジェネリクスは入っていないかもしれないし、入っていても全然文法が違うかもしれないので注意してください。
でもとりあえず Golang にジェネリクスを導入するのは、かなり前向きに検討されているのは確実なので今後に期待ですね。