Menu
Coddy logo textTech

ジェネリクス入門

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}) を呼び出すことができます。コンパイラはコンパイル時に型をチェックするため、実行時のオーバーヘッドなしに安全性を得ることができます。

今後のレッスンでは、型パラメータ、制約、およびジェネリック構造体について詳しく見ていきます。

challenge icon

チャレンジ

簡単

ジェネリクスを使用して、整数と浮動小数点の両方のスコアに対応するスコアアナライザーを作成しましょう!スライス内の最大値を見つけることができる単一のジェネリック関数を作成し、異なる数値型に対して重複したコードを作成する必要をなくします。

コードは2つのファイルに分けて構成します:

  • analyzer.go: ジェネリックな分析関数を定義します。

    スライス内の最大値を見つけて返すジェネリック関数 Max[T int | float64](values []T) T を作成してください。この関数は、型パラメータ T を通じて intfloat64 の両方の型で動作する必要があります。

    また、スライス内のすべての値の合計を計算して返すジェネリック関数 Sum[T int | float64](values []T) T も作成してください。

  • main.go: 入力を読み取り、異なる型でジェネリック関数を実演します。

    型インジケーター(int または float)を読み取り、次にカウント、その後にその数だけの値を読み取ります。型に従って値をパースし、適切な型で MaxSum の両方を呼び出し、結果を出力します。

    整数の入力の場合、次のように出力します:

    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] を使用することで、ロジックを一度書くだけで、コンパイラがそれぞれの使用法に対して型安全なコードを生成できるようになります。

チートシート

ジェネリクスを使用すると、型安全性を維持しながら、複数の型で動作する再利用可能なコードを記述できます。Go 1.18より前は、型ごとに個別の関数が必要だったか、型アサーションを伴う空のインターフェースを使用していました。

型パラメータは角括弧内で宣言され、ジェネリック関数が受け入れることができる型を指定します。

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})           // intで動作します
Min([]float64{3.5, 1.2})      // 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: 小数点第1位までを "Max (float): [value]" および "Sum (float): [value]" の形式で出力する
	}
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン