
2026/08/01 3:39
Golang プロポーザル:container/ パッケージでのジェネリックコレクション型
RSS: https://news.ycombinator.com/rss
要約▶
Japanese 翻訳:
次の改善版では、特定のデータ構造を統合し、Go 1.28 とそれ以前のリリース間の範囲を明確にし、主要な要点からアーキテクチャの詳細を取り入れています。
サマリー:
Go 1.28 は、新しい Go Collections API のための主要な汎用リリースとして機能し、言語の歴史的な順序付きマップおよびセットの欠如に対応しています。このイニシアチブは、
container/ordered.Map(挿入順序による格納を実現するために平衡二分木を実装)、container/set.Set(Union および Intersection などの正規操作をサポート)、そしてカスタムハッシュ関数を利用する container/hash.Map/Set を含む複数の重要な提案を統合します。このリリースでは、標準のヒープ実装を置き換えるための container/heap/v2 も導入され、container/mapset のようなレガシーヘルパーも提供されています。
API デザインは、特定のアーキテクチャ選択を通じて安全性と効率性を最優先しています:二分木のパフォーマンスを維持するため
DeleteFunc を維持しつつ、Set インターフェースから Subset を除外して過度な負担を避けるという方針を取っています。特徴的なのは、異なるセット型の互換性のない Union メソッドが共通の通常のインターフェースを共有しないように(「バイナリメソッド問題」を解決)、安全に二分探索メソッドを取り扱うために F 限界多型を採用している点です。挿入順序付きハッシュマップなどのいくつかの提案は将来のリリースのために検討中である一方で、このツールのコレクションはデータ構造の標準化を行い、エコシステムの分断を軽減すると同時に、Go の厳格な後方互換性を損なうことなく深い比較機能を 제공합니다。本文
Go Collections: 標準ライブラリへの新たなコレクション API
背景と目的
Go Collections ワーキンググループは、2025 年末に設立されました。
- 設立趣旨: Go の「実践主義」と「簡潔性」の原則に基づき、共通のコレクションデータ構造を標準ライブラリに取り入れることを目的としています。
- 主要メンバー:
- Jonathan Amsterdam (@jba)
- Alan Donovan (@adonovan)
- Robert Griesemer (@griesemer)
- Daniel Martí (@mvdan)
- Roger Peppe (@rogpeppe)
- Keith Randall (@khr)
- Ian Lance Taylor (@ianlancetaylor)
- 現状の課題:
- 標準ライブラリにはスライスとマップしか提供されておらず、ヒープ以外の実装は限られています。
は完全に欠けており、慣習として集合
またはmap[T]bool
が使用されています。map[T]struct{}- 順序付けされたマップやセットも存在しません。
- ジェネリックとイテレータの進展:
- Go 1.18 でジェネリック、Go 1.23 でイテレータ機能が追加され、ライブラリ型でも組み込み型に近い使いやすさが実現可能になりました。
- 本作業は標準ライブラリに重要なデータ構造を追加し、API の慣習を設定することを目的としています。
提案される新しい API
以下のように、複数の新しい型やパッケージが追加予定されています。
| Issue # | CL 番号 | パッケージ/型名 | 概要 |
|---|---|---|---|
| #70471 | CL 657296 | | go1.27 リリース済み。カスタムハッシュ関数および等価性のインターフェースを提供します。 のコンパイル時定義とは異なり、キー型が比較できない場合や深い比較が必要になる場合に有用です。 |
| #69559 | CL 612217 | | カスタムハッシュ関数を使用したハッシュベースのマップ型。 |
| #80584 | CL 741160 | | ハッシュベースのセット型。 |
| #69230 | CL 745441 | | 要素が比較可能なセットのための標準的なデータ型。 内部では を透明に使用し、並合 (Union) や共通部分 (Intersection) などの演算をサポートします。 |
| #77052 | CL 724420 | | レガシーセットのヘルパー。既存 API ではセットとしての操作が難しい場合、Union や Intersection などの演算を提供します。 のメソッドと並行して動作します。 |
| #60630 | - | | 順序付きマップ。内部は現在平衡バイナリツリーを使用していますが、必須ではありません。 「マップを作成してキーをソートする」手法よりも範囲クエリなどで優れています。 |
| #77397 | - | | 新しいヒープ実装。既存の使いにくいヒープを置き換えるジェネリックなバイナリヒープ API です。 |
将来の計画: 挿入順序を保証するハッシュマップ (#80194) やスタックなどの追加も検討中です。
初期実装のアプローチ: API と漸近的性能(時間計算量)を満たすことに焦点を当て、定数係数の最適化は後回しとしています。
抽象的なコレクション制約インターフェース
Map および Set タイプの共有メソッドを実現するため、共通のインターフェース型が必要です。しかし、「二項式メソッド問題」のため、通常のインターフェースでは記述できません(各実装型のメソッドシグネチャが互換性を持たないため)。
そのため、F 境界多形を使用する抽象的な制約インターフェースが導入されました。
- 非公開: 現時点で公式に公開されておりませんが、一貫性を保つための内部利用のみ可能です。
- 将来性: 具体的なコレクション型の経験に基づき、将来的なリリースで公開される可能性があります。
インターフェース定義の例
テストにおける適合性保証のみを目的とした簡易的な再掲です。
// _AbstractCollection は要素 E の集合 C(*hash.Map, *hash.Set など)を表します。 type _AbstractCollection[E any, C _AbstractCollection[E, C]] interface { Clear() Clone() C Contains(E) bool ContainsAll(iter.Seq[E]) bool Len() int String() string } // _AbstractMap はキー K から値 V へのマッピング M を表します。 type _AbstractMap[K, V any, M _AbstractMap[K, V, M]] interface { _AbstractCollection[K, M] All() iter.Seq2[K, V] At(K) V Delete(K) (V, bool) DeleteAll(iter.Seq[K]) bool DeleteFunc(func(K, V) bool) bool Get(K) (V, bool) Keys() iter.Seq[K] Set(K, V) (V, bool) SetAll(iter.Seq2[K, V]) bool Values() iter.Seq[V] } // _AbstractSet は要素 E の集合 S を表します。 type _AbstractSet[E any, S _AbstractSet[E, S]] interface { _AbstractCollection[E, S] All() iter.Seq[E] Delete(E) bool DeleteAll(iter.Seq[E]) bool DeleteFunc(func(E) bool) bool Difference(S) S DifferenceWith(S) Equal(S) bool Insert(E) bool InsertAll(iter.Seq[E]) bool Intersection(S) S IntersectionWith(S) Intersects(S) bool SymmetricDifference(S) S SymmetricDifferenceWith(S) Union(S) S UnionWith(S) }
汎用関数の定義例
最小限の制約型を定義することで、ユーザーが必要な操作を実装可能です。
// _TakeSet は [Take] 関数に必要な抽象的なセットを定義します。 type _TakeSet[E any, S _TakeSet[E, S]] interface { All() iter.Seq[E] Delete(E) bool } // Take はセットから任意の要素を取り出し返します(空の場合はゼロ値)。 func Take[S _TakeSet[E, S], E any](set S) (e E, found bool) { for e = range set.All() { found = true set.Delete(e) break } return }
メソッド選択に関する判断基準
- 主観性とバランス: すべての操作をインターフェースに含めると実装負担が不釣り合いになるため、実装者への影響を考慮して慎重な選択が行われています。
メソッドは省略されました(高速化が必要な特殊ケースを除き、コストがかかるため)。Subset
は残されています(ツリー内の要素削除における漸近的パフォーマンス低下を防ぐため、O(n log n) を O(n) にするため)。DeleteFunc
- 将来の拡張: 具体的な使用状況に基づき、必要なメソッドを追加していく予定です。
その他の設計上の考察
メソッドの返戻値と再検索防止
- 変異メソッド: コレクションサイズを変更したかどうかを報告します。
- 置換ロジック (
/Map.Set
): 既存のキーがあればその値を返し、ブール値で存在の有無も区別します(ゼロ値との混同を防ぐため)。Map.Delete - 取得メソッド (
vsGet
):At
は式文脈での使い勝手を考慮し提供されています。At
マップとセットの違い
- マップ:
メソッドを持っていません(値が比較不可能になり得るため)。Equal - 置換ルール:
は既存のキーを持つエントリを置換します(組み込みのMap.Set
に従う)。map
集合演算のパフォーマンスと実装手法
集合代数演算(Union など)は、純粋関数的に新しいセットを返す方式を採用しました。
- 理由:
API の経験を踏まえ、副作用や結果の忘却を防ぐためです。math/big.Int - バリエーション:
オプションなし: 左演算子を不変として新しいセットを返します(「便利なもの」)。-With
オプションあり: 左演算子を直接変更し、何もしません(割当効率の良いもの)。-With
キーセットビュー
というラッパー型により、マップKeySetView[M, K, V]
のキーセットとして[K,V]
を抽象的に満たすことができます。Set[K]- このビューには挿入は無意味であり、パニックを引き起こします。
maps
パッケージとの機能比較
maps| 抽象 API | 代替手段 / 対応関係 |
|---|---|
, | 組み込み関数 (, ) で代替可能 |
, , , , | 完全に並行 ( など) |
, , | 組み込み演算子 (, ループ) で代替可能(#67377 の提案も参照) |
| で代替可能 |
今後、「
maps.{Contains, ContainsAll, DeleteAll}」の追加提案を検討する可能性があります。
: 式文脈においてContains
よりも有用です。_ , ok = s[k]
,ContainsAll
: ループおよびブール値の管理を不要にする利点があります。DeleteAll