ジェネリクス入門
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 67/107。
Go 1.18 より前は、異なる型で再利用可能なコードを書くには、関数を複製するか、空のインターフェース any と型アサーションを使う必要がありました。ジェネリクスは、完全な型安全性を維持しながら、複数の型で動作する関数や型を書けるようにすることで、この問題を解決します。
slice の最小値を求めることを考えてみましょう。generic がなければ、型ごとに個別の関数が必要になります。
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
}ジェネリックを使うと、角括弧内の型 parameterを使用して、ロジックを一度だけ記述できます。
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}) を呼び出せます。コンパイラーはコンパイル時に型をチェックするため、実行時のオーバーヘッドなしで安全性を得られます。
これからのレッスンでは、型パラメーター、制約、ジェネリック構造体について詳しく学びます。
チャレンジ
簡単genericを使って、整数と浮動小数点数のスコアの両方に対応するスコア analyzerを作りましょう!slice内の最大値をfindする単一のgeneric functionを作成し、異なる数値typeごとに重複したコードを書く必要をなくします。
コードを2つのファイルに整理します。
analyzer.go:generic analysis functionを定義します。slice内の最大値をfindしてreturnsするgeneric function
Max[T int | float64](values []T) Tを作成します。この functionは、type parameterTを通じてintとfloat64の両方のtypeで動作します。また、slice内のall valuesのsumをcalculatesしてreturnsするgeneric function
Sum[T int | float64](values []T) Tも作成します。main.go:inputをReadし、異なるtypeでgeneric functionsを実演します。type indicator(
intまたはfloat)をReadし、その後にcountと、count個のvaluesをReadします。typeに応じてvaluesをParseし、適切なtypeでMaxとSumの両方をCallして、resultsをPrintします。integer inputの場合、次のようにPrintします。
Max (int): [value] Sum (int): [value]float inputの場合、小数点以下1桁でPrintします。
Max (float): [value] Sum (float): [value]
次のinputが提供されます。
- Line 1:Type indicator(
intまたはfloat) - Line 2:valuesのcount(integer)
- Following lines:1行につき1つのvalue
たとえば、次のinputが与えられた場合:
int
4
15
8
23
11outputは次のようになります。
Max (int): 23
Sum (int): 57また、次のinputが与えられた場合:
float
3
4.5
9.2
6.8outputは次のようになります。
Max (float): 9.2
Sum (float): 20.5同じgeneric functionsがinteger dataとfloating-point dataの両方をどのように処理しているかに注目してください。type parameter [T int | float64]によって、compilerは各usageに対してtype-safeなcodeを生成できます。その一方で、あなたが書くlogicは1回だけです。
自分で試してみよう
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: 小数点以下1桁で "Max (float): [value]" および "Sum (float): [value]" の形式で結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ