【Go】Go ver1.18 からジェネリクスが使えるようになった【ジェネリクス】

## はじめに
2022年3月なので、既に数ヶ月以上前の話にはなりますが…
Go のver1.18がリリースされ、新機能としてジェネリクスが使えるようになりました。
現在私が仕事で触っているのはver1.16なのでジェネリクスの恩恵を受けていないのですが
どうやらものすごく便利な機能らしいので少し触ってみようかなと思います。

#### 編集履歴
**2022.08.02**
・ver1.18以前、interface{}の弊害としてのpanicに関する記述を追加
・「ジェネリクスが追加されて個人的に嬉しくなったこと（補足）」を追加
・参考リンク追加

## ジェネリクスとは？？
>ジェネリック（総称あるいは汎用）プログラミング（英: generic programming）は、具体的なデータ型に直接依存しない、抽象的かつ汎用的なコード記述を可能にするコンピュータプログラミング手法である。 wikipediaより

~~ちょっと何言ってるか分からない。~~

静的型付け言語ではお馴染みの機能らしいが、ver1.18以前のGoにはなかった機能。
超ざっくり言うと、関数とかで汎用的に（generic）型を受け付けるような形で定義できるみたい。
~~動的型付け言語ばっかり触ってたからよく分からない~~

## ver1.18以前
リリースノートでの例を使って触ってみる。環境は、[前回使ったDocker環境](https://qiita.com/tsuchinoko0102/items/529cc93e55f87604ca34 "【Go】Goで配列を爆速で処理してくれるライブラリが素敵だった【koazee】")がたまたま1.18だったので使い回し。


```go:main.go（ver1.18以前の記法）
package main

import "fmt"

func Min(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

func main() {
	fmt.Println(Min(3, 2)) // 2
}
```
2つの値の小さい方を出力するだけのとてもシンプルな`Min関数`を出力するだけのもの。
ですが、2つ受け取る引数はどちらも、float64じゃないと受け取ってくれません。
 ```go
func main() {
	var x int = 3
	var y int = 2
	fmt.Println(Min(x, y))
}
```
なんてしてみると
```go
./main.go:15:18: cannot use x (variable of type int) as type float64 in argument to Min
./main.go:15:21: cannot use y (variable of type int) as type float64 in argument to Min
```
はぁ！？int型なんて受け取れないんですけど？？と怒られます。

ver1.18以前は、真っ当に書こうとすると、引数及び戻り値ごとに関数を作ってあげるのが正着。
```go
func Float64Min(x, y float64) float64 {
    if x < y {
        return x
    }
    return y
}

func IntMin(x, y int) int {
	if x < y {
		return x
	}
	return y
}
```

あるいは、`interface{}型`という何でもアリな型があったので例えば
```go
func Min(x, y interface{}) float64 {
	switch x.(type) {
	case int:
		switch y.(type) {
		case int:
			if x.(int) < y.(int) {
				return float64(x.(int))
			}
			return float64(y.(int))
		}
	case float64:
		switch y.(type) {
		case float64:
			if x.(float64) < y.(float64) {
				return x.(float64)
			}
			return y.(float64)
		}
	}
	return 0
}
```
とかやれば、**超無理やり**どうにかすることができた。
~~適当に書いたのでもうちょっとマシな書き方できたと思う。~~ 
ただ、`x.(floay64)` のように型キャストだらけで気持ち悪い。

この程度の関数ならまだしも、たとえばlistとかmapとかで中身の型がなんであれ同じような挙動をさせたい関数だと、引数の型ごとに関数を作るのはとてもめんどくさいです。

そして、`interface{}`で逃げるともう1つ困ることがあります。それは、**panic**です。
```go:わざとpanicが出すような書き方にしてみた例
func Int64PlusOne(x interface{}) int64 {
	return x.(int64) + 1
}
func main() {
    var name1 string = "伊達朱里紗"
    fmt.Println(Int64PlusOne(name1))
    // panic: interface conversion: interface {} is string, not int64
```
明らかに`int64`しか受け付けなさそうな`Int64PlusOne`関数の仮引数を`interface{}`にしてみたことで
`string型`の`name1`を実引数に`Int64PlusOne`を実行しようとしても、**コンパイルでは検知できません。**
実行した後にpanicを起こしてプログラムが停止します。予期せぬ不具合に繋がりそうで嫌ですね。
ちなみに私はpanicが本当に嫌いです。~~（私がパニックになるから）~~


そこで登場したのがジェネリクス。リリースノートの記述からして、Golang最大級のアップグレードらしい。

## ver1.18から使用できる記法
```go:main.go（変更箇所のみ）
func GenericMin[T constraints.Ordered](x, y T) T {
	if x < y {
		return x
	}
	return y
}

func main() {
	var a float64 = 100.88
	var b float64 = 200.7
	fmt.Println(GenericMin(a, b)) // 100.88

	var x int = 3
	var y int = 2
	fmt.Println(GenericMin(x, y)) // 2
	
	var name1 string = "伊達朱里紗"
	var name2 string = "岡田紗佳"
	fmt.Println(GenericMin(name1, name2)) // 伊達朱里紗（バイト単位で「伊」の方が「岡」より小さい）
}
```
`GenericMin`の方では、先程のように仮引数に`float64`も`interface{}`も置いていませんが
`main.go`で呼び出す側の実引数で型を明示して渡してあげることで、`int`も`float64`も、何なら`string`でも`GenericMin`を使うことができました。

## 具体的に何が変わったのか
`GenericMin[T constraints.Ordered]` の`[]`のような記法は、1.18以前には存在しませんでした。
これは**型パラメータ**というもので、`[]`から推測できる通り、宣言した関数で使用できる型を「型パラメータ**リスト**」において定義するというもの。
関数宣言の段階で、**使用できる型に制約**を与えてあげることで、複数の型で使用できる関数を定義できる、ということのようです。
上記の例では、Goのサブリポジトリから`constraints.Ordered`（大小比較できる、intとかfloatとかそういうやつ）を`GenericMin関数`の型パラメータとして、関数宣言を行っています。

これにより、上述のpanicの問題も解消されます。
`GenericMin`を呼び出している側で宣言された型が有効なものかどうか、という判定をコンパイルで済ませてしまうからです。
~~panicざまぁ~~


```go
fmt.Println(GenericMin[float64](64.22, 34.89)) // 34.89
```
という風に、`GenericMin`を呼び出す時に`[]`を使って型を明示することもできます。
このように、ジェネリックに作った関数に型引数を与えることを**インスタンス化**といいます。

前述の通り型パラメータはリストなので、こんな感じにすることもできます。
```go:main.go
// float64 か int を受けつける型パラメータに変更
func GenericMinExceptStr[T float64 | int ](x, y T) T { 
	if x < y {
		return x
	}
	return y
}

func main() {
	var a float64 = 100.88
	var b float64 = 200.7
	fmt.Println(GenericMinExceptStr(a, b)) // 100.88

	var x int = 3
	var y int = 2
	fmt.Println(GenericMinExceptStr(x, y)) // 2

	fmt.Println(GenericMinExceptStr[float64](64.22, 34.89)) // 34.89

    var name1 string = "伊達朱里紗"
	var name2 string = "岡田紗佳"
	fmt.Println(GenericMinExceptStr(name1, name2))
    // string型は受け付けてないので、「コンパイルの段階で」エラーになる
}
```

## ジェネリクスが追加されて個人的に嬉しくなったこと（補足）
最近（**2022.07.22**）になり、mapやslice周りの関数が追加されました。

たとえば、jsとかではごく当たり前に使われている`Object.keys(obj)`みたいな連想配列からキーをまとめて取得するみたいなメソッド。
**ver1.18以前のGoにはありませんでした。**
forループ使ったりして自作するしかなかったんですね。下記コードは他所から丸々もらってきました。（参考リンク）

```go
package main

import (
	"fmt"
)

func main() {
	persons := map[string]int{"伊達": 30, "岡田": 29, "瑞原": 35, "二階堂": 40}

	// personsの全部のkeyを取得する処理
	var keys []string
	for key := range persons {
		keys = append(keys, key)
	}

	fmt.Println(keys) // [伊達 岡田 瑞原 二階堂]
}
```
Goではmapでもsliceでも宣言時に内容物の型定義が必要なので、それらすべてに対応する形でpackage側で関数作れなかった（作るのがめんどくさかった）から無かったんだろうと思いますが。

ところが、Goのサブリポジトリである`golang/x/exp`パッケージが更新され、`maps`や`slices`といったものが追加、他言語でお世話になりがちな関数の一部が使用できるようになりました。

```go:golang/x/exp/maps を使ってmapのkeyを取得する
package main

import (
    "fmt"
    "golang/x/exp/maps"
)

func main() {
	persons := map[string]int{"伊達": 30, "岡田": 29, "瑞原": 35, "二階堂": 40}
	fmt.Println(maps.Keys(persons)) // [伊達 岡田 瑞原 二階堂]
}
```
はい、神アプデ。
せっかくなので、`maps.Keys()`の定義を覗いてみましょう。
```go
func Keys[M ~map[K]V, K comparable, V any](m M) []K
```
思いっ切りジェネリクスの恩恵受けまくりな関数定義です。素敵です。

`~map[K]V`の`~`は、`Underlying Type`というものを示し、基礎となる型`がmap[K]V`であることを要求するもの、という意味合いとのことです。（詳細は参考リンクへ）

`K comparable`で出てくる`comparable型`は、**型パラメータでのみ使用できる**インターフェースで、文字列・数値・真偽値・ポインタなど**比較可能な連中**の総称（？）のようなイメージです。

`V any`の`any`は、ver1.18以前の`interface{}`です。というか、ver1.18でしれっと`interface{}`のエイリアスとして`any`が追加されました。~~interface{}って、{}があって気持ち悪かったもんね。~~

ジェネリクスが登場したことで、これまで他言語では簡単だったのにGoでは難しかったことが簡単に出来るようになるパッケージが実装されたり、パッケージ周りの盛り上がりも期待できるかもしれません。


## 最後に
Go自身が最大級のアップグレードと言うだけのことがある、かなり使い勝手の良さそうな変更点という印象を受けました。
特にpanic大嫌いな私としては、これまでinterface{}に頼ってpanicを起こしていた部分をコンパイル時点で判別して修正できるってのは最高に気持ちよさそうだなと思います。

あとは、少し紹介したmapsやslicesがもっと充実すると実装が楽になるんだけどなぁなんて思ったり。


## 参考リンク
**Go公式 ジェネリクスの紹介**
https://go.dev/blog/intro-generics

**当記事の内容について、100倍くらい深く解説されている記事**
zenn Nobishii様
https://zenn.dev/nobishii/articles/type_param_intro
https://zenn.dev/nobishii/articles/type_param_intro_2

**ver1.18以前の方法でmapからキーの一覧の取り出し方を紹介されている記事**
https://asapoon.com/golang/5399/how-to-get-map-keys/

**型パラメータのチルダについての解説**
zenn waawaa様
https://zenn.dev/waawaa/articles/ac07cf53a69cccca156c