前提条件
- Go 1.18 以降のインストール。 インストール手順については、Goのインストールを参照してください。
- コードを編集するためのツール。 お使いのテキストエディタで問題ありません。
- コマンドターミナル。 GoはLinuxやMacの任意のターミナル、WindowsのPowerShellやcmdでうまく動作します。
コード用のフォルダを作成する
まず、書くコード用のフォルダを作成します。
- 1. コマンドプロンプトを開き、ホームディレクトリに移動します。
LinuxまたはMacの場合:
Windowsの場合:$ cd
チュートリアルの残りの部分では、プロンプトとして$が表示されます。使用するコマンドはWindowsでも動作します。C:\> cd %HOMEPATH%
- 2. コマンドプロンプトから、コード用のディレクトリをgenericsという名前で作成します。
$ mkdir generics
$ cd generics
- 3. コードを保持するためのモジュールを作成します。
go mod init
コマンドを実行し、新しいコードのモジュールパスを指定します。
注意: 本番コードの場合、より具体的なモジュールパスを指定します。詳細については、依存関係の管理を参照してください。$ go mod init example/generics
go: creating new go.mod: module example/generics
非ジェネリック関数を追加する
このステップでは、マップの値を合計して合計を返す2つの関数を追加します。
異なるタイプのマップ(int64
の値を格納するものと、float64
の値を格納するもの)を扱っているため、1つの関数ではなく2つの関数を宣言しています。
コードを書く
- 1. テキストエディタを使用して、genericsディレクトリにmain.goという名前のファイルを作成します。このファイルにGoコードを書きます。
- 2. main.goのファイルの先頭に、次のパッケージ宣言を貼り付けます。
スタンドアロンプログラム(ライブラリとは対照的に)は常にパッケージpackage main
main
にあります。 3. パッケージ宣言の下に、次の2つの関数宣言を貼り付けます。
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
このコードでは、
- マップの値を合計して合計を返す2つの関数を宣言します。
SumFloats
は、string
からfloat64
の値へのマップを受け取ります。SumInts
は、string
からint64
の値へのマップを受け取ります。
4. main.goの先頭で、パッケージ宣言の下に、2つのマップを初期化し、前のステップで宣言した関数を呼び出すときに引数として使用するための
main
関数を貼り付けます。func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
}
このコードでは、
float64
の値のマップとint64
の値のマップを初期化し、それぞれ2つのエントリを持ちます。- 以前に宣言した2つの関数を呼び出して、各マップの値の合計を求めます。
- 結果を印刷します。
5. main.goの先頭近くで、パッケージ宣言のすぐ下に、先ほど書いたコードをサポートするために必要なパッケージをインポートします。
最初のコード行は次のようになります:package main
import "fmt"
- 6. main.goを保存します。
コードを実行する
main.goを含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Non-Generic Sums: 46 and 62.97
ジェネリクスを使用すると、ここで2つの代わりに1つの関数を書くことができます。次に、整数または浮動小数点値を含むマップ用の単一のジェネリック関数を追加します。
複数の型を処理するためのジェネリック関数を追加する
このセクションでは、整数または浮動小数点値を含むマップを受け取る単一のジェネリック関数を追加します。これにより、先ほど書いた2つの関数を1つの関数に置き換えます。
どちらの型の値をサポートするために、その単一の関数はサポートする型を宣言する方法が必要です。一方、呼び出しコードは、整数または浮動小数点マップで呼び出しているかを指定する方法が必要です。
これをサポートするために、通常の関数パラメータに加えて型パラメータを宣言する関数を書きます。これらの型パラメータにより、関数はジェネリックになり、異なる型の引数で動作できるようになります。関数を型引数と通常の関数引数で呼び出します。
各型パラメータには、型パラメータのメタ型として機能する型制約があります。各型制約は、呼び出しコードがそれぞれの型パラメータに使用できる許可された型引数を指定します。
型パラメータの制約は通常、型のセットを表しますが、コンパイル時には型パラメータは呼び出しコードによって型引数として提供された単一の型を表します。型引数の型が型パラメータの制約で許可されていない場合、コードはコンパイルされません。
型パラメータは、ジェネリックコードがその上で実行するすべての操作をサポートする必要があることを忘れないでください。たとえば、関数のコードが数値型を含む型パラメータに対してstring
操作(インデックス付けなど)を試みると、コードはコンパイルされません。
これから書くコードでは、整数または浮動小数点型のいずれかを許可する制約を使用します。
コードを書く
- 1. 以前に追加した2つの関数の下に、次のジェネリック関数を貼り付けます。
このコードでは、// SumIntsOrFloats sums the values of map m. It supports both int64 and float64
// as types for map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
- 2つの型パラメータ(角括弧内)
K
とV
を持つSumIntsOrFloats
関数を宣言し、型map[K]V
のm
という型パラメータを使用する1つの引数を持ちます。この関数は型V
の値を返します。 K
型パラメータにcomparable
型制約を指定します。このようなケースに特に意図されたcomparable
制約は、Goで事前に宣言されています。これは、比較演算子==
および!=
のオペランドとして使用できる値を持つ任意の型を許可します。Goはマップのキーが比較可能であることを要求します。したがって、K
をcomparable
として宣言することは、マップ変数のキーとしてK
を使用できるようにするために必要です。また、呼び出しコードがマップキーに許可された型を使用することを保証します。V
型パラメータに、int64
とfloat64
の2つの型の和集合である制約を指定します。|
を使用すると、2つの型の和集合が指定され、この制約はどちらの型も許可します。どちらの型も、呼び出しコードの引数としてコンパイラによって許可されます。m
引数がmap[K]V
型であることを指定します。ここで、K
とV
は型パラメータに対してすでに指定された型です。map[K]V
が有効なマップ型であることは、K
が比較可能な型であるため、わかります。K
を比較可能として宣言しなかった場合、コンパイラはmap[K]V
への参照を拒否します。
- 2. main.goで、すでにあるコードの下に次のコードを貼り付けます。
このコードでは、fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
コードを実行する
main.goを含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
コードを実行するには、各呼び出しでコンパイラが型パラメータをその呼び出しで指定された具体的な型に置き換えました。
あなたが書いたジェネリック関数を呼び出す際に、型引数を指定してコンパイラに関数の型パラメータの代わりに使用する型を伝えました。次のセクションで見るように、多くの場合、これらの型引数を省略できます。コンパイラはそれらを推測できます。
ジェネリック関数を呼び出す際に型引数を削除する
このセクションでは、呼び出しコードを簡素化するために、ジェネリック関数呼び出しの修正バージョンを追加します。型引数を削除しますが、これはこの場合には必要ありません。
Goコンパイラが使用したい型を推測できる場合、呼び出しコードで型引数を省略できます。コンパイラは関数引数の型から型引数を推測します。
ただし、これは常に可能ではありません。たとえば、引数がないジェネリック関数を呼び出す必要がある場合、関数呼び出しに型引数を含める必要があります。
コードを書く
- main.goで、すでにあるコードの下に次のコードを貼り付けます。
このコードでは、fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
コードを実行する
main.goを含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
次に、整数と浮動小数点の和集合をキャプチャして、他のコードから再利用できる型制約にします。
型制約を宣言する
この最後のセクションでは、以前に定義した制約を独自のインターフェースに移動し、複数の場所で再利用できるようにします。このように制約を宣言することで、制約がより複雑な場合にコードを簡素化できます。
型制約をインターフェースとして宣言します。この制約は、インターフェースを実装する任意の型を許可します。たとえば、3つのメソッドを持つ型制約インターフェースを宣言し、それをジェネリック関数の型パラメータで使用すると、関数を呼び出すために使用される型引数はすべてのメソッドを持っている必要があります。
制約インターフェースは、特定の型を参照することもできます。このセクションで見るように。
コードを書く
- 1.
main
のすぐ上に、インポート文の直後に、型制約を宣言するために次のコードを貼り付けます。
このコードでは、type Number interface {
int64 | float64
}
- 型制約として使用する
Number
インターフェース型を宣言します。 - インターフェース内に
int64
とfloat64
の和集合を宣言します。
本質的に、関数宣言から和集合を新しい型制約に移動しています。これにより、int64
またはfloat64
のいずれかに型パラメータを制約したい場合、このNumber
型制約を使用できます。int64 | float64
を書く必要はありません。
- 2. すでにある関数の下に、次のジェネリック
SumNumbers
関数を貼り付けます。
このコードでは、// SumNumbers sums the values of map m. It supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
- 以前に宣言したジェネリック関数と同じロジックを持つジェネリック関数を宣言しますが、型制約として和集合の代わりに新しいインターフェース型を使用します。以前と同様に、引数と戻り値の型に型パラメータを使用します。
- 3. main.goで、すでにあるコードの下に次のコードを貼り付けます。
このコードでは、fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
コードを実行する
main.goを含むディレクトリのコマンドラインから、コードを実行します。
$ go run .
Non-Generic Sums: 46 and 62.97
Generic Sums: 46 and 62.97
Generic Sums, type parameters inferred: 46 and 62.97
Generic Sums with Constraint: 46 and 62.97
結論
素晴らしい!Goのジェネリクスに初めて触れました。
次にお勧めのトピック:
- Goツアーは、Goの基本を段階的に紹介する素晴らしいリソースです。
- Effective GoやGoコードの書き方に記載されている有用なGoのベストプラクティスを見つけることができます。
完成したコード
このプログラムはGoプレイグラウンドで実行できます。プレイグラウンドでは、単に実行ボタンをクリックしてください。
package main
import "fmt"
type Number interface {
int64 | float64
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
}
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}