【Go言語】ジェネリクス

# はじめに

今回はGoのジェネリクスの使い方をまとめました。
スライドもあります。

<script defer class="speakerdeck-embed" data-id="5e2b90ed97b64564947251b42096fedd" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

# any

## anyとは

anyとは空インターフェイス`interface{}`のエイリアスで、どんな型でも保持することが可能。

以下は実際にanyにいろんな型を入れてみた例。

[Go Playground](https://go.dev/play/p/DDtuiq_VxwV)

```go
func main() {
	var i any
	i = 42 // int
	i = "foo" // string

	i = struct {
		s string
	}{
		s: "bar",
	} // struct

	i = func() {} //関数

	_ = i
}
```

## anyのデメリット

* 過剰な一般化
* 静的型付け言語の利点が失われる

anyは何の情報も持たないので基本的には使わない方がいい。

以下のようにanyを使うと、anyの引数を受け取り、anyを返すので、メソッドの表現力に欠ける。また、型検査がないので、危ない。

```go
type A struct {}
type B struct {}
type C struct {}
func (c *C) Get(id string)(any, error) {}
func (c *C) Set(id string, v any) error {}
```

Goは静的型付け言語であるので、Goの利点が失われる。
Goではシグニチャをできるだけ明示的にする。

```go
func (c *C) GetContract(id string) (Contract, error) {}
func (c *C) SetContract(id string, contract Contract) error {}
func (c *C) GetCustomer(id string) (Contract, error) {}
func (c *C) SetCustomer(id string, contract Contract) error {}
```

また、クライアントはinterfaceで以下のように抽象化を行える。

```go
type ContractC interface {
    GetContract(id string) (c.Contract, error)
    SetContract(id string, c.Contract) error
}
```

## anyを使うべきとき

マーシャル関数など、あらゆる型を受け取ったり返したりする必要があるときは、anyを用いる。

以下は、標準ライブラリから、関数やメソッドがanyの引数を受け取る例。

[encoding/jsonのMarshal](https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/encoding/json/encode.go;l=159-170#:~:text=163-,164,-165)

```go
func Marshal(v any) ([]byte, error) {
	e := newEncodeState()
	defer encodeStatePool.Put(e)

	err := e.marshal(v, encOpts{escapeHTML: true})
	if err != nil {
		return nil, err
	}
	buf := append([]byte(nil), e.Bytes()...)

	return buf, nil
}
```

ただし、interface同様、**過剰に一般化させるのは避けるべき**である。

[database/sqlのQueryContext](https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/database/sql/sql.go;l=1727-1737#:~:text=1736-,1737,-1738)でもanyが使用されている。

```go
func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error) {
	var rows *Rows
	var err error

	err = db.retry(func(strategy connReuseStrategy) error {
		rows, err = db.query(ctx, query, args, strategy)
		return err
	})

	return rows, err
}
```

# ジェネリクス

ジェネリクスを使用すれば、anyによる過剰な一般化を避けることができます。

## ジェネリクスを使用しない例

以下の例は、map型からキーを全て出力する。

[Go Playground](https://go.dev/play/p/lL6T8BDRE3D)

```go
func main() {
	m := map[string]int{
		"太郎": 25,
		"次郎": 30,
		"花子": 35,
	}
	keys := getKeys(m)
	fmt.Print(keys) // [太郎 次郎 花子]
}

func getKeys(m map[string]int) []string {
	var keys []string
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}
```

この例では、map[string]intではなく、map[int]stringなどキーと値が他の型であった場合に動かなくなる。

## anyで実装した例

[Go Playground](https://go.dev/play/p/9WgO0MVtYe_P)

anyを利用して、以下のようにキーと値がどのような型でも受け取れるようにする。

```go
func getKeys(m any) []any {
	switch t := m.(type) {
	default:
		return nil
	case map[string]int:
		var keys []any
		for k := range t {
			keys = append(keys, k)
		}
		return keys
	case map[int]string:
		var keys []any
		for k := range t {
			keys = append(keys, k)
		}
		return keys
	case map[int]int:
		return nil
	}
}
```

anyを使って実装すると、`getKeys`が異なる型で同じ処理をしたい場合、同じコードが増えてしまう。これはDRY原則に反する。また、anyを受けとり、anyを返すので、Goの静的型付け言語の利点が失われている。

## comparableを使う例

インターフェイス同様anyによる無駄な抽象化は避けるべきである。
mapでは、`var m map[[]byte]int`のように、keyにスライスを取ることはできない。keyをany型の代わりに**comparable** を用いて以下のようにする。[Go Playground](https://go.dev/play/p/ENyS1GKu3IU)

```go
func getKeys[K comparable, V any](m map[K]V) []K {
	var keys []K
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}
```

`T comparable`とは、組み込みのインターフェイス。`T comparable`は `==` や `!=` で比較可能な値のみ引数に受け取ることが可能になる。

## 独自の型を使う

unionsを用いて、インターフェイスで独自の型制約を定義することもできる。[Go playground](https://go.dev/play/p/VHKfcvYQNua)

```go
type customConstraint interface {
	~int | ~string
}

func getKeys[K customConstraint, V any](m map[K]V) []K {
	var keys []K
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}
```

ちなみに、unionsを含むインタフェースは型制約でしか使えません。`var x customConstraint`のようにはできない。

## ジェネリクスの注意

メソッドでは使用できない。(関数は可能)

```go
type Foo struct {}

// ./main.go:29:15: methods cannot have type parameters
func (Foo) bar[T any](t T) {}
```

structでは使用できる。

```go
type Node[T any] struct {
   Val  T
   next *Node[T]
}

func (n *Node[T]) Add(next *Node[T]) {
   n.next = next
}
```

以下は、独自の型制約をstructに持たせた例
[Go Playground](https://go.dev/play/p/LpN7woWoX18)

```go
func main() {
	taro := Person[int]{age: 10}
	taro.f()
	jiro := Person[string]{age: "ten"}
	jiro.f()
}

type T interface {
	~int | ~string
}

type Person[T any] struct {
	age T
}

func (p Person[T]) f() {
	fmt.Println(p.age)
}
```

interfaceにstrcutを埋め込むこともできる。しかし、フィールドにはアクセスできない。

```go
type I interface {
	Person
}

type Person struct {
	age  int
	name string
}

func f[T I](x T) {
	fmt.Printf("%v", x)  // x.nameにするとコンパイルエラー
}

func main() {
	x := Person{age: 10, name: "Taro"}
	f(x)
}
```

以下のようにしても同様にコンパイルエラー。

```go
func f[T Person](x T) {
	fmt.Printf("%v", x.name)
}
```

ジェネリクスはあくまでも型制約のみで、実際にフィールドにアクセスすることはできない。

## ~int v.s. int

~intはintをベースとする型を全て受け取れる。

[Go Playground](https://go.dev/play/p/E3kcU9-7TJ6)

```go
type CustomInt int

func Add[T ~int](a, b T) T {
	return a + b
}

func main() {
	var a, b int = 1, 2
	fmt.Println(Add(a, b)) // 3

	var c, d CustomInt = 3, 4
	fmt.Println(Add(c, d)) // 7
}
```

## 使用すべきとき

* データ構造(e.g. バイナリツリー、連結リスト、ヒープ)
* スライス、マップ、および任意のタイプのチャネルで動作する関数

```go
func merge[T any](ch1, ch2 <-chan T) <-chan T {
    // ...
}
```

* ジェネリクスを使って振る舞いを抽象化する

以下はsortパッケージでの例

```go
type sliceFn[T any] struct {
   s       []T
   compare func(T, T) bool
}

func (s sliceFn[T]) Len() int           { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.compare(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int)      { s.s[i], s.s[j] = s.s[j], s.s[i] }
```

## ジェネリクスを使用すべきではないとき

* 受け取った引数のメソッドを使用するとき

```go
func foo[T io.Writer](w T) {
   b := getBytes()
   _, _ = w.Write(b)
}
```

* ジェネリクスを使うことでコードが複雑になるとき
  * Goの開発者は10年間ジェネリクスなしでやってきた

# 参考文献

1. [Being confused about when to use generics (#9) - 100 Go Mistakes and How to Avoid Them](https://100go.co/9-generics/)
1. [Type Parameters Proposal](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md)
1. [Go言語のジェネリクス入門](https://zenn.dev/nobishii/articles/type_param_intro#go%E3%81%AE%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9%E3%81%AE%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%89%87)