Goのジェネリクス先取り入門2

Yuji Kubota
Voicy Engineering
Published in
4 min readDec 14, 2020

この記事は Voicy Advent Calendar 2020 の 14日目の記事です。

昨日は @kikakuaykoumonさんの
感情価値向上のマーケティング活動・Emotional Value 「人が動く時。それは、 情報ではなく、感情で動く。」
です。

この記事は9日目のこちらの記事の続きです。Goに今後導入予定のジェネリクスについて、2020/11/25日に出されたドラフトを読みながら先取りしてしまおうという内容ですが、仕様については今後変わる可能性がありますので、その点をご了承いただいた上で読んでいただければと思います。

型パラメータの限定

前回の記事までは型パラメータには any を使用してきましたが、実際には指定できる型を限定したいことがあります。その場合はインターフェース定義内でtypeの後ろにカンマ区切りで型リストを指定することができます。

type SignedInteger interface {
type int, int8, int16, int32, int64
}

型リストを指定したインターフェースは通常のインターフェースのような使い方はできず、型パラメータの定義でのみ使用可能です。

型を指定することで得られるメリットは他にもあります。例えば配列に含まれる値の中で最小値を取得するメソッドを作成する場合、以下のようなコードが考えられます。しかしこの場合は v < r の部分でコンパイルエラーになってしまいます。

func Smallest[T any](s []T) T {
r := s[0] // panic if slice is empty
for _, v := range s[1:] {
if v < r { // INVALID
r = v
}
}
return r
}

これは、 < による比較を全ての型がサポートしているわけではないからです。しかし、先ほど作成した SignedInteger でしていた int 系の型はすべて < による比較をサポートしています。そのため、型パラメータの [T any] の部分を [T SignedInteger] に変更することでコンパイルすることができます。

型推論

例えば以下のような関数があった場合

func Map[F, T any](s []F, f func(F) T) []T { ... }

これを呼び出す際には

var s []int
f := func(i int) int64 { return int64(i) }
var r []int64

r = Map[int, int64](s, f)

のと書くことができますが、型推論を使うと以下のどちらの書き方も可能となります。

// 最初の型のintのみを指定
r = Map[int](s, f)
// どちらも指定しない
r = Map(s, f)

自分自身を参照する型パラメータ

例えば以下のようなメソッドを作成する場合、

func Index[T Equaler](s []T, e T) int {
for i, v := range s {
if e.Equal(v) {
return i
}
}
return -1
}

Equaler インターフェースを別途作成する必要がありますが、その簡単な方法として、このメソッドの定義を

func Index[T interface { Equal(T) bool }](s []T, e T) int {・・・}

とすることができます。これは T の型パラメータの定義ないで、自分自身を参照するインターフェースを定義しています。

もちろん、これとは別で Equaler インターフェースを定義することもできますが、その際は Index 関数では Equaler に対して型を指定してあげる必要があります。

type Equaler[T any] interface {
Equal(T) bool
}
func Index[T Equaler[T]](s []T, e T) int {・・・}

最後に

いかがでしたでしょうか。ドラフトには他にもいろいろなことが書いてありますが、ひとまず前半部分からいくつかピックアップしてみました。

待望のジェネリクスですが、その先にはGo2が待ち構えていますので、これからもGoの進化が楽しみでなりません。今後も新しい情報はどんどんキャッチアップしていきたいと思います!

--

--