ジェネリクス入門
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 67/107。
Go 1.18 より前は、異なる型に対応する再利用可能なコードを書くには、関数を重複して書くか、型アサーションと空のインターフェース any を使う必要がありました。ジェネリクスを使うと、型安全性を完全に保ちながら、複数の型で動作する関数や型を書けるため、この問題を解決できます。
スライス内の最小値を見つけることを考えてみましょう。ジェネリクスがなければ、型ごとに別々の関数が必要になります。
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}ジェネリクスを使うと、角括弧の中に型パラメーターを指定して、ロジックを一度だけ書けます。
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}[T int | float64]は、intまたはfloat64のいずれかを取る型パラメーターTを宣言します。これで、同じ関数を使ってMin([]int{3, 1, 2})やMin([]float64{3.5, 1.2})を呼び出せます。コンパイラーはコンパイル時に型をチェックするため、実行時のオーバーヘッドなしで安全性を得られます。
これからのレッスンでは、型パラメーター、制約、ジェネリック構造体について詳しく学びます。
チャレンジ
簡単ジェネリクスを使って、整数スコアと浮動小数点スコアの両方に対応するスコア分析器を作りましょう!スライス内の最大値を見つけられる汎用関数を1つ作成し、数値型ごとにコードを重複して書く必要をなくします。
コードは2つのファイルに分けて整理します。
analyzer.go:汎用の分析関数を定義します。スライス内の最大値を見つけて返す汎用関数
Max[T int | float64](values []T) Tを作成します。この関数は、型パラメーターTを通じて、int型とfloat64型の両方で動作します。また、スライス内のすべての値の合計を計算して返す汎用関数
Sum[T int | float64](values []T) Tも作成します。main.go:入力を読み取り、異なる型で汎用関数を実演します。型の指定(
intまたはfloat)を読み取り、続けて個数と、その個数分の値を読み取ります。型に応じて値を解析し、適切な型でMaxとSumの両方を呼び出して、結果を出力します。整数が入力された場合は、次のように出力します。
Max (int): [value] Sum (int): [value]浮動小数点数が入力された場合は、小数点以下1桁で出力します。
Max (float): [value] Sum (float): [value]
入力は次の形式で与えられます。
- 1行目:型の指定(
intまたはfloat) - 2行目:値の個数(整数)
- 以降の行:1行につき1つの値
たとえば、次の入力が与えられた場合:
int
4
15
8
23
11出力は次のようになります。
Max (int): 23
Sum (int): 57また、次の入力が与えられた場合:
float
3
4.5
9.2
6.8出力は次のようになります。
Max (float): 9.2
Sum (float): 20.5同じ汎用関数が整数データと浮動小数点データの両方を扱えることに注目してください。型パラメーター [T int | float64] によって、ロジックを一度書くだけで、コンパイラーが用途ごとに型安全なコードを生成できます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 型を示す値を読み込む
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// 個数を読み込む
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// 整数値を読み込む
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: 整数のスライスを使ってMaxとSumを呼び出す
// TODO: 結果を"Max (int): [value]"と"Sum (int): [value]"の形式で出力する
} else if typeStr == "float" {
// 浮動小数点数を読み込む
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: float64のスライスを使ってMaxとSumを呼び出す
// TODO: 結果を"Max (float): [value]"と"Sum (float): [value]"の形式で小数点以下1桁まで出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ