0001-01-01

Go の新しい Generics のデザインはなぜこんなに「歪」なのか

Go の新しい Generics のデザインが出ました。

https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md

興味深い点が多くあるので原文を読むことをおすすめしますが、ざっくり言うと以下のような構文によって型パラメータが導入されることになります。

type Float interface {
	type float32, float64
}

func Sqrt(type T Float)(v T) T {
    ...
}

一見すると2019年の Contracts のデザインよりもすっきりし、他のジェネリクスがある言語のように直感的になったように見えますが、実は他の言語と同じように考えているとハマりそうな落とし穴があります。そこには Go ならではの事情があるのですが、今日はそのあたりの話をします。

interface と contracts が統合されたわけではない

表面的には contract という予約語は消え、interface と同一の存在になったように見えますが、実はそんなことはありません。

例えば以下のようなコードが例としてデザインドキュメントに示されています。

type integer interface {
	type int, int8, int16, int32, int64,
		uint, uint8, uint16, uint32, uint64, uintptr
}

func Add10(type T integer)(s []T) {
	for i, v := range s {
		s[i] = v + 10
	}
}

type int, ... は Type List と呼ばれるもので、一言で言えば直和型のようなものです。

しかし、それならば、シンプルにこうは書けないのか?

type integer interface {
	type int, int8, int16, int32, int64,
		uint, uint8, uint16, uint32, uint64, uintptr
}

func Add10(s []integer) {
	for i, v := range s {
		s[i] = v + 10
	}
}

結論を言えば、これはできません[*1]。Type List を使ってしまった時点で、その interface は contract としてしか使えなくなってしまうのです。

同様に以下のようなコードも不正となります。

var n integer

以上のような例から、新しいデザインでは contract と interface が一見ひとつのものになったものの、概念的にはまだ別物だということがわかります。つまり「contract は interface のスーパーセットである(しかし同一ではない)」という点で、2019年のデザインドキュメントから変わっていないのです。

しかしなぜこんなことになっているのでしょうか。Type List を単純な直和型として定義してしまえば、後方互換性を壊さず、より直感的になり、contract という概念は消えて言語仕様的にもシンプルになるはずです。

// TODO

単純な直和型が Go で実現出来ない理由

一言で言えば「コンパイル時に」