Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
ジェネリクス
ジェネリクス(型パラメータ)を使うと、型を抽象化した汎用的な関数や型を定義できます。Go 1.18で導入され、コードの再利用性が大幅に向上しました。
構文
// 型パラメータ付き関数を定義します。
func 関数名[T 型制約](引数 T) T {
// 処理
}
// 型パラメータ付き構造体を定義します。
type 型名[T 型制約] struct {
フィールド T
}
// 複数の型制約を定義します(インターフェースで表現します)。
type 制約名 interface {
型1 | 型2 | 型3
}
主な型制約
| 制約 | 概要 |
|---|---|
| any | すべての型を受け入れます。『interface{}』と同等です。 |
| comparable | 『==』と『!=』で比較可能な型に限定します(スライスやマップは含まれません)。 |
| ~T | 型Tを基底型に持つすべての型を含みます(独自型も対象になります)。 |
| int | float64 | ユニオン型制約です。指定した型のいずれかを受け入れます。 |
| constraints.Ordered | 『golang.org/x/exp/constraints』パッケージで定義される順序比較が可能な型です。 |
サンプルコード
package main
import "fmt"
// 数値型の制約を定義します。
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
// ジェネリックな合計関数です。
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
// ジェネリックなマップ変換関数です。
func Map[T, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = f(v)
}
return result
}
// ジェネリックなフィルタ関数です。
func Filter[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) {
result = append(result, v)
}
}
return result
}
// 型パラメータ付きのスタック構造体です。
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}
// comparableを使ったジェネリック関数です。
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
func main() {
// Sum関数を異なる型で使います。
ints := []int{1, 2, 3, 4, 5}
floats := []float64{1.1, 2.2, 3.3}
fmt.Println(Sum(ints)) // 『15』と出力されます。
fmt.Println(Sum(floats)) // 『6.6』と出力されます。
// Map関数で整数を文字列に変換します。
nums := []int{1, 2, 3}
strs := Map(nums, func(n int) string {
return fmt.Sprintf("item%d", n)
})
fmt.Println(strs) // 『[item1 item2 item3]』と出力されます。
// Filter関数で偶数だけを取り出します。
evens := Filter(ints, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // 『[2 4]』と出力されます。
// ジェネリックなスタックを使います。
s := &Stack[string]{}
s.Push("Go")
s.Push("Generics")
v, ok := s.Pop()
fmt.Println(v, ok) // 『Generics true』と出力されます。
// Contains関数を文字列スライスに使います。
langs := []string{"Go", "Python", "Rust"}
fmt.Println(Contains(langs, "Go")) // 『true』と出力されます。
fmt.Println(Contains(langs, "Java")) // 『false』と出力されます。
}
概要
ジェネリクスを使うと、型ごとに同じ処理を繰り返し書かずに済みます。例えば、整数のスライスと浮動小数点数のスライスを同じ関数で処理できます。型推論が働くため、ほとんどの場合は型パラメータを明示せずに呼び出せます。
型パラメータ付きの関数はメソッドとして定義できません。型パラメータを追加できるのは関数と型定義のみです。
『comparable』制約は『==』による比較が可能な型(int、string、構造体など)に限定します。スライスやマップは比較不可のため含まれません。ジェネリクスの濫用はコードを複雑にするため、明確な型安全性の向上が見込める場面に限定して使用することを推奨します。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。