なぜ Go 言語にコレクションクラスがないんだ!そうだ。 AI に作らせよう。
という訳で、Gemini だけで ArrayList をGo で作らせてみました。
そのときの内容を6回に分けてお伝えしていこうと思います。
- 連載リンク
- 第一回 まずは作ってもらった
- 第二回 制約つきの構造体を追加してみた (このページ)
- 第三回 Collection で定義されているメソッドを追加してみた
- 第四回 List で定義されているメソッドを追加してみた
- 第五回 Sort もしたい
- 第六回 Geminiとの対話で生まれたコードとまとめ
第二回 制約つきの構造体を追加してみた
分割して掲載する都合上、実際に質問を投げた順番と掲載の順番とを少々入れ替えてあります。
Geminiからは、毎回コード全体が出力されましたが、
記事が長くなりすぎてしまうため、変更があった部分だけをお送りいたします。
Contains メソッド
質問
contains メソッドはリストに指定した値があるかどうかを返すもの。
値が一致するかどうかを判断できるようにするには型制約をanyではなくcomparableにする必要がある。
ただ、MyArrayList構造体の制約をanyからcomparableに変更するだけだと、== で比較できない型はMyArrayListで使うことができなくなるので、
もうひとつ別の構造体をつくってくれないかなぁ。
以下のように聞いてみました。
MyArrayList[comparable] を持った構造体を作って contains メソッドを追加してください
「MyArrayList[comparable] を持った構造体」というのは「フィールド MyArrayList[comparable] を持っている構造体」のつもりでした。
なのに。。。
回答
承知いたしました。
comparable制約を持つMyArrayList構造体と、containsメソッドを追加します。Goのジェネリクスにおける
comparable制約は、その型が==や!=演算子で比較可能であることを示します。これにより、containsメソッドで要素の比較を行うことができます。以下に修正したコードを示します。
package main import "fmt" // MyArrayList はJavaのArrayListに似た機能を提供するジェネリックな動的配列です。 // comparable制約を追加し、要素が比較可能であることを示します。 type MyArrayList[T comparable] struct { elements []T } // Contains は指定された要素がリストに含まれている場合にtrueを返します。 func (list *MyArrayList[T]) Contains(element T) bool { for _, item := range list.elements { if item == element { // comparable制約により比較が可能 return true } } return false } func main() { stringList := NewMyArrayList[string]() stringList.Add("apple") stringList.Add("banana") stringList.Add("cherry") fmt.Println("Original List:", stringList) // Containsメソッドのテスト fmt.Println("Contains 'banana'?", stringList.Contains("banana")) // true fmt.Println("Contains 'grape'?", stringList.Contains("grape")) // false fmt.Println("Contains 'apple'?", stringList.Contains("apple")) // true stringList.Remove(0) fmt.Println("List after removal:", stringList) fmt.Println("Contains 'apple'?", stringList.Contains("apple")) // false intList := NewMyArrayList[int]() intList.Add(10) intList.Add(20) intList.Add(30) fmt.Println("Int List:", intList) fmt.Println("Contains 20?", intList.Contains(20)) // true fmt.Println("Contains 50?", intList.Contains(50)) // false // 構造体など、comparableではない型をMyArrayListで使おうとするとコンパイルエラーになる // type MyStruct struct { // Name string // Value int // } // structList := NewMyArrayList[MyStruct]() // コンパイルエラー: MyStruct does not satisfy comparable }変更点と解説
MyArrayList[T comparable]:
MyArrayListの型パラメータTにcomparable制約を追加しました。- これにより、
MyArrayListを使用する際に、その要素の型が==や!=演算子で比較可能であることがコンパイル時に保証されます。- プリミティブ型(
int,string,boolなど)、ポインタ、チャネル、インターフェース、構造体(すべてのフィールドがcomparableである場合)、配列(要素がcomparableである場合)などがcomparableです。- スライス、マップ、関数はcomparableではありません。
Contains(element T) boolメソッドの追加:
- このメソッドは、引数
elementがリストlist.elements内に存在するかどうかをチェックします。for _, item := range list.elementsでリストの各要素をイテレートします。if item == elementの部分で、comparable制約のおかげで==演算子を使って要素同士を比較できます。- 一致する要素が見つかれば
trueを返し、ループが終了しても見つからなければfalseを返します。
comparable制約の重要性
comparable制約がない場合、Containsメソッド内でitem == elementのような比較を行うと、コンパイルエラーになります。Goのジェネリクスは、型パラメータが特定の操作をサポートすることを明示的に宣言する必要があります。comparable制約は、その型が比較可能であることを保証するためのものです。これで、
MyArrayListはJavaのArrayListの主要な機能に加えて、要素の存在チェックも行えるようになりました。
感想
おうふっ
見事にやってほしくないことをやってくれました。
anyをcomparableに変更しただけ。
MyComparableArrayList 構造体
質問
もうひとつ別の構造体を作ってほしい。
どう聞けばいいのか分からず、コードでそのまま聞いてみました。
type MyArrayList[T any] struct { elements []T }
と
type MyComparableArrayList[T comparable] struct { MyArrayList[T] }
のふたつにしてください
回答
承知いたしました。
MyArrayListとMyComparableArrayListの2つの構造体に分け、MyComparableArrayListがMyArrayListを埋め込む形で実装します。これにより、すべての型で利用可能な基本的な
MyArrayListと、比較可能な型に限定されるもののContainsメソッドを持つMyComparableArrayListという、役割が明確な2つの構造体を提供できます。2つの構造体に分割した実装
package main import "fmt" // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // NewMyArrayList は新しいMyArrayListを初期化して返します。 func NewMyArrayList[T any]() *MyArrayList[T] { return &MyArrayList[T]{ elements: make([]T, 0), } } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // NewMyComparableArrayList は新しいMyComparableArrayListを初期化して返します。 func NewMyComparableArrayList[T comparable]() *MyComparableArrayList[T] { return &MyComparableArrayList[T]{ MyArrayList: *NewMyArrayList[T](), // 埋め込んだMyArrayListを初期化 } } // Contains は指定された要素がリストに含まれている場合にtrueを返します。 // Tがcomparableなので、要素の比較が可能です。 func (list *MyComparableArrayList[T]) Contains(element T) bool { for _, item := range list.elements { // MyArrayListのelementsフィールドにアクセス if item == element { return true } } return false } func main() { fmt.Println("--- MyArrayList の使用例 ---") // 任意の型 (ここでは構造体) を格納できる MyArrayList type Person struct { Name string Age int } personList := NewMyArrayList[Person]() personList.Add(Person{Name: "Alice", Age: 30}) personList.Add(Person{Name: "Bob", Age: 25}) fmt.Println("Person List:", personList) fmt.Println("Size:", personList.Size()) p, err := personList.Get(0) if err == nil { fmt.Println("First Person:", p.Name) } // MyArrayListはContainsメソッドを持たない (comparableではないため) // personList.Contains(Person{Name: "Alice", Age: 30}) // コンパイルエラー fmt.Println("\n--- MyComparableArrayList の使用例 ---") // 文字列 (comparable) を格納できる MyComparableArrayList stringList := NewMyComparableArrayList[string]() stringList.Add("apple") stringList.Add("banana") stringList.Add("cherry") fmt.Println("String List:", stringList) fmt.Println("Size:", stringList.Size()) // Containsメソッドの使用 fmt.Println("Contains 'banana'?", stringList.Contains("banana")) // true fmt.Println("Contains 'grape'?", stringList.Contains("grape")) // false // MyArrayListから継承されたメソッドも使える val, err := stringList.Get(1) if err == nil { fmt.Println("Get(1):", val) } removed, err := stringList.Remove(0) if err == nil { fmt.Println("Removed:", removed) } fmt.Println("String List after removal:", stringList) fmt.Println("Size after removal:", stringList.Size()) fmt.Println("Contains 'apple'?", stringList.Contains("apple")) // false // int (comparable) を格納できる MyComparableArrayList intList := NewMyComparableArrayList[int]() intList.Add(10) intList.Add(20) fmt.Println("Int List:", intList) fmt.Println("Contains 20?", intList.Contains(20)) // true }変更点と解説
MyArrayList[T any] struct:
- この構造体は、型パラメータ
Tがanyであるため、任意の型の要素を格納できます。Add,Get,Remove,Size,IsEmpty,Clear,String,Sliceといった基本的な動的配列の機能を提供します。Containsメソッドは持っていません。なぜなら、Tがanyである場合、Tの要素が比較可能である保証がないからです。MyComparableArrayList[T comparable] struct:
- この構造体は、型パラメータTが
comparable制約を持ちます。これにより、T型の要素が==や!=で比較できることが保証されます。MyArrayList[T]を埋め込みフィールドとして持ちます。
- Goの構造体の 埋め込み(embedding) 機能により、
MyComparableArrayListはMyArrayListが持つすべてのメソッド(Add,Get,Removeなど)を直接呼び出すことができます。これはJavaの継承に似た振る舞いを実現します。MyComparableArrayListのインスタンスに対してlist.Add(...)のように呼び出すと、内部で埋め込まれたMyArrayListのAddメソッドが実行されます。NewMyComparableArrayList: このコンストラクタでは、埋め込まれたMyArrayListも*NewMyArrayList[T]()で適切に初期化する必要があります。Contains(element T) boolメソッド:
Tがcomparableであるため、この構造体でのみContainsメソッドを実装できます。- 内部の
elementsスライス(埋め込みによってアクセス可能)を走査し、item == elementで比較を行います。この設計のメリット
- 役割の分離:
MyArrayListは汎用的な動的配列の基盤を提供し、comparableでない型(例: スライス、マップ、関数、独自定義だが比較できない構造体)も安全に格納できます。MyComparableArrayListは、要素の比較が必要な場合に特化し、そのためのContainsメソッドを追加します。- コードの再利用:
MyComparableArrayListはMyArrayListの機能を埋め込みによって再利用するため、コードの重複が避けられます。- 型安全性:
Containsメソッドが必要な場合にのみcomparable制約を課すことで、より厳密な型チェックがコンパイル時に行われます。この構造により、必要に応じて適切な
ArrayListのバリアントを選択できるようになりました。
感想
そうそう。
これが欲しかったんです!
まとめ
当然ですがAIとの間で認識が違っていると、なかなか思い通りの回答を出してくれません。
根気よく言い換えていけばいずれ理解してくれるはずです。