
Go言語のジェネリクス機能を使ってみました
-
2022年7月22日
お疲れ様です。開発のOです。
少し前までC++を使っておりましたが、新規のプロジェクトにてGo言語を使うことになり、絶賛勉強中です。
今まで弊社ではver.1.17だったのですが、折角の新規プロジェクトですのでver.1.18まであげて、このバージョンより実装されたジェネリクスを使ってみました。
C++で言うところのテンプレートですね。
基本的な使い方
主に、関数で使う場合と型に使う場合の二つがあります。
C++でいうところのメンバ関数テンプレートなどは使えないです。
他言語と同じように定義名の後にデータ型名定義を[]で囲んで記載します。(他の言語は<>が多い印象ですが、Goでは[]…Why?)
関数の場合
- 宣言
func f[T any](prm T) T { ・・・ }
- 使い方
func test(){ f[int](10) f(10) ・・・ }
↑[int]は省略可能
型の場合
- 宣言
type SampleStruct[T any] struct { prm1 T prm2 T }
↑例としてstructにしてますが、type SampleSlice[T any] []Tとかでも可能です。
- 使い方
func test(){ t := TestType[float32]{ 10.0, 20.0, } ・・・ }
構造体メソッドに適用する場合
- 使い方
type SampleType[T any] struct { } func (t *SampleType[T]) SampleFunc(prm T) T{ ・・・ }
型制約
上記の例で[T any]のように型名の後ろにanyと記載されていますが、こちらは型制約と言われるものでTがどのような型なのかを指定しています。
anyは何でもとれる型を表しています。
型制約はinterfaceで定義します。
ex1.
type SampleInterface interface{ Print() string } func Sample[T SampleInterface](s T){ s.Print() }
また、 ver.1.18より定義されたunionsという文法をつかって以下のように複数の型を定義できます。
ex2.
type Integer interface { int | int8 | int16 | int32 | int64 }
ただ、もしintを下記のように別途定義している場合、SampleIntを指定できません。
type SampleInt int func f[T Integer](prm T){} func test(){ var a SampleInt f(a) ← NG!! }
その場合は~をつけると指定できるようになります。
~をつけるとunderlying typeと言われる大元の型を参照するようになります。
ex3.
type Integer2 interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 } type SampleInt int func f2[T Integer2](prm T){} func test(){ var a SampleInt f2(a) ← OK!! }
型制約についてはver.1.18から追加されたconstraintsパッケージに色々と定義されているので見てみてください。
上で記載したInteger2もconstraints.Signedとして定義されているので、既にあるものを再実装しないように注意しましょう。
利用例
- Pairの実装
ちょっとしたPair的なものを作りたいときに。
type Pair[K, V any] struct { Key K Value V }
- Slice系の便利関数
今まで汎用的なsliceの処理はinterface{}で渡してキャストするといった工夫が必要でしたが、ジェネリクスにより簡単に実装できるようになりました。また、ver.1.18からslicesパッケージやmapsパッケージが追加されており、ジェネリクスを利用した汎用関数がいくつか実装されています。既にある機能はそこを利用するのがいいでしょう。
//ConvertSlices []Tを[]Uへ変換 func ConvertSlices[T, U any](srcList []T, convertFunc func(T) U) []U { var result []U for _, v := range srcList { result = append(result, convertFunc(v)) } return result }
まとめ
C++ほど色々なことはできませんが、逆にシンプルにわかりやすく使えると思います。(というかC++は頑張りすぎるとカオスになりがちな印象…)
ただ、コンパイル時に型決まるっぽい(多分…間違ってたらすいません)ので、あまりやりすぎるとコンパイル時間が伸びるのでそこだけは要注意ですね。
また、ライブラリなどの場合はver.1.18以前のGoを使っているプログラムでは当然動かないので、その辺の互換性も考慮に入れて作らないといけないですね。
ココネでは一緒に働く仲間を募集中です。
ご興味のある方は、ぜひこちらの採用特設サイトをご覧ください。