サンプルで Go: ジェネリクス

Go のバージョン 1.18 から、型パラメータとも呼ばれるジェネリクスのサポートが追加されました。

package main
import "fmt"

ジェネリック関数の例として、MapKeys は任意の型のマップを受け取ってキーのスライスを返します。この関数は KV の 2 つの型パラメータを持ちます。K には comparable制約があり、つまりこの型の値を ==!= 演算子で比較できます。これは Go のマップキーには必須です。V には any の制約があり、つまりまったく制限がありません (anyinterface{} のエイリアスです)。

func MapKeys[K comparable, V any](m map[K]V) []K {
    r := make([]K, 0, len(m))
    for k := range m {
        r = append(r, k)
    }
    return r
}

ジェネリック型の例として、List は任意の型の値を持つ単一リンクリストです。

type List[T any] struct {
    head, tail *element[T]
}
type element[T any] struct {
    next *element[T]
    val  T
}

ジェネリック型に対するメソッドは、通常の型に対する場合と同じように定義できますが、型パラメータはそのままにしておく必要があります。この型は List ではなく List[T] です。

func (lst *List[T]) Push(v T) {
    if lst.tail == nil {
        lst.head = &element[T]{val: v}
        lst.tail = lst.head
    } else {
        lst.tail.next = &element[T]{val: v}
        lst.tail = lst.tail.next
    }
}
func (lst *List[T]) GetAll() []T {
    var elems []T
    for e := lst.head; e != nil; e = e.next {
        elems = append(elems, e.val)
    }
    return elems
}
func main() {
    var m = map[int]string{1: "2", 2: "4", 4: "8"}

ジェネリック関数を呼び出すときは、多くの場合型推論に頼ることができます。MapKeys の呼び出し時に KV の型を指定する必要がないことに注意してください。コンパイラはこれらの型を自動的に推論します。

    fmt.Println("keys:", MapKeys(m))

…明示的に指定することもできます。

    _ = MapKeys[int, string](m)
    lst := List[int]{}
    lst.Push(10)
    lst.Push(13)
    lst.Push(23)
    fmt.Println("list:", lst.GetAll())
}
$ go run generics.go
keys: [4 1 2]
list: [10 13 23]

注意: Go ではマップキーの反復順序は定義されていないので、呼び出しのたびに異なる順序になることがあります。

次の例: エラー