Go/ジェネリクス
ジェネリクス[編集]
Go言語では、ジェネリクスが導入される前は、インターフェースや具体的な型に依存した実装が主流でした。 ジェネリクスが導入されても、従来の方法も引き続き使用できます。
interfaceを使った実装[編集]
以下はその一例として、ジェネリクスを使わずにスタックを実装する方法を示します。
- 非ジェネリクス版スタック
package main import ( "fmt" ) // スタックの構造体 type Stack struct { data []interface{} } // スタックに要素を追加 func (s *Stack) Push(item interface{}) { s.data = append(s.data, item) } // スタックから要素を取り出す func (s *Stack) Pop() interface{} { item := s.data[len(s.data)-1] s.data = s.data[:len(s.data)-1] return item } // スタックの先頭の要素を取得(削除はしない) func (s *Stack) Peek() interface{} { return s.data[len(s.data)-1] } // スタックが空かどうかをチェックするメソッド func (s *Stack) IsEmpty() bool { return len(s.data) == 0 } func main() { // 空のスタックを作成 stack := Stack{} // スタックに値を追加 stack.Push(10) stack.Push("Hello") stack.Push(3.14) // スタックの内容を表示 for !stack.IsEmpty() { item := stack.Pop() fmt.Println("Popped:", item) } }
- 実行結果
Popped: 3.14 Popped: Hello Popped: 10
この例では、Stack
という構造体を定義し、Push
で要素を追加し、Pop
で要素を取り出します。interface{}
型を使用することで、任意の型の要素をスタックに追加できます。ただし、実行時に型アサーションを使用して型を確認する必要があります。
型パラメータを使った実装[編集]
Go言語におけるジェネリクス(型パラメータ)を利用したスタックの実装は次のようになります。
- ジェネリクス版スタック
package main import ( "fmt" ) // ジェネリクス化されたスタックの定義 type Stack[T any] []T // スタックに要素を追加するメソッド func (s *Stack[T]) Push(item T) { *s = append(*s, item) } // スタックから要素を取り出すメソッド func (s *Stack[T]) Pop() T { item := (*s)[len(*s)-1] *s = (*s)[:len(*s)-1] return item } // スタックが空かどうかをチェックするメソッド func (s *Stack[T]) IsEmpty() bool { return len(*s) == 0 } func main() { // int型のスタックを作成 var intStack Stack[int] // スタックに値を追加 intStack.Push(1) intStack.Push(2) intStack.Push(3) // スタックから値を取り出して表示 for !intStack.IsEmpty() { item := intStack.Pop() fmt.Println(item) } // 文字列型のスタックを作成 var stringStack Stack[string] // スタックに値を追加 stringStack.Push("apple") stringStack.Push("banana") stringStack.Push("cherry") // スタックから値を取り出して表示 for !stringStack.IsEmpty() { item := stringStack.Pop() fmt.Println(item) } }
- 実行結果
3 2 1 cherry banana apple
このコードは、Go言語のジェネリクスを使ってスタックを実装しています。Stack[T any]
という定義は、T
という任意の型の要素を持つスタックを表します。
Push
メソッドはスタックに要素を追加し、Pop
メソッドはスタックから要素を取り出します。IsEmpty
メソッドはスタックが空かどうかを確認します。
main
関数では、intStack
とstringStack
という異なる型のスタックを作成し、それぞれのスタックに値を追加してから、要素を取り出して表示しています。これにより、異なる型の要素を持つジェネリックなスタックが正しく機能していることが示されています。
このコードを実行すると、intStack
には1、2、3が、stringStack
には"apple"、"banana"、"cherry"が順番に追加され、それらがスタックから取り出されて画面に表示されます。
型制約[編集]
1.18で追加された予約済識別子 any
は interface{}
の alias で全ての型と一致します。
ただ、何でもかんでも any にすると必要なメソッドを備えていない型のインスタンスまで受け付けてしまいます。 この問題を解決するために型制約が用意されています。
Go言語における型制約は、主にインターフェースを通じて行われます。Goにはジェネリクス制約を直接指定する仕組みはありませんが、インターフェースを使って特定の振る舞いを持つ型を制約することができます。
例えば、fmt.Stringer
はString()
メソッドを持つ型を表すインターフェースです。これは標準パッケージの中で多く利用されます。これにより、fmt.Print
などの関数はfmt.Stringer
インターフェースを満たす型を引数に受け取ることができます。
- 型制約
package main import ( "fmt" ) type Bin int func (b Bin) String() string { return fmt.Sprintf("%#b", int(b)) } func Print[T fmt.Stringer](s []T) { for _, v := range s { fmt.Print(v, "\n") } } func main() { Print([]Bin{0b1101, 0b1010_0101}) }
- 実行結果
0b1101 0b10100101
このコードは、Go言語でのジェネリクスに型制約を加える例です。Bin
という新しい型を定義し、Bin
型に対してString()
メソッドを実装しています。String()
メソッドは、Bin
型を2進数文字列として表現するためのものです。
Print
関数は、fmt.Stringer
インターフェースを実装した任意の型のスライスを受け取り、その要素をfmt.Print
を使って画面に出力する関数です。fmt.Stringer
インターフェースは、String()
メソッドを持つ型に適用されます。
main
関数では、Print
関数を使ってBin
型のスライスを表示しています。Print
関数はfmt.Stringer
インターフェースを満たすため、Bin
型のスライスを受け取ることができ、Bin
型の各要素はString()
メソッドを介して2進数文字列として出力されます。
最終的に、main
関数ではPrint
関数を使って[]Bin{0b1101, 0b1010_0101}
を表示しています。これにより、Bin
型の値が2進数文字列として正しく出力されることが確認できます。
Goのジェネリクスの特徴 |
Go言語におけるジェネリクスの導入は、バージョン1.18で行われました。ジェネリクスの導入により、より柔軟で再利用可能なコードを書くことができるようになりました。以下に、Goのジェネリクスの特徴を解説します。
Goのジェネリクスは、これまでのバージョンよりも柔軟性と再利用性を向上させ、型安全性を保ちながら汎用性の高いコードを書くことができるようにしました。これにより、より洗練された、効率的で堅牢なプログラムを開発することができます。 |