Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go 1.26 Genericsにおける再帰的型制約 / Recursive Type Con...

Go 1.26 Genericsにおける再帰的型制約 / Recursive Type Constraints in Go 1.26 Generics

LayerX.go #4 の登壇で使用した資料です。
イベントページ→ https://layerx.connpass.com/event/383847/

スライド内のリンクにアクセスしたい方はこちらからご覧ください→https://docs.google.com/presentation/d/e/2PACX-1vS5eyB5ab4el-K2s-reVXU-j6JA0URNDseLscpJUp1nbX94P31W2bti8V0P0vOiEB3h9IBurdfxA3f2/pub?start=false&loop=false&delayms=30000

Avatar for turbofish

turbofish

March 11, 2026
Tweet

More Decks by turbofish

Other Decks in Technology

Transcript

  1. 自己紹介 X: @turbofish_ 所属:株式会社ZOZO マーケティングオートメーション( MA)部所属で、配信基盤システムを開発す るお仕事。GoやPython、Google Cloudをよく使う。 最近の登壇・執筆など •

    Software Design 2026年3月号: 第1特集 再考・ログ設計 第3章 • Go Conference 2025: GoのinterfaceとGenericsの内部構造と進化 • Google Cloud Next Tokyo ‘25: ZOZOTOWN の大規模マーケティング メール配信を 支えるアーキテクチャ
  2. Go1.26以前にもできたこと // 構造体での型パラメータリスト指定 type Concrete[T Normal[T]] struct { Data T

    } // interfaceでの型パラメータリスト指定 type Normal[T any] interface { Do(T) } ↑ 書き方は似てるけど再帰的ではない
  3. 再帰的型制約(Go1.26~) type Adder[A Adder[A]] interface { Add(A) A } func

    algo[A Adder[A]](x, y A) A { return x.Add(y) } 出所:Go1.26 Release Notes ↑ これができるようになった
  4. 余談:Genericメソッド(Proposal Accepted) type S struct { … } func (*S)

    m[P any](x P) { … } var s S s.m[int](42) // 明示的な型変数 int s.m(x) // 型引数Pはxから推論される 出所:Issue #77273 spec: generic methods for Go レシーバの型定義を変えずに、メソッドの引数や返り値をパラメータ化できる ↑ Pの制約はanyなのでどの型でも受け入れるが、コンパイル時に型情報 が維持されるため、関数内で型アサーションを使う必要がない
  5. 型理論における多相の種類 ←[T any] Generics型の再帰的型制約により赤 字が実現できるようになる 多相 アドホック多相 パラメトリック多相 サブタイプ多相 無限パラメトリック多相

    有界パラメトリック多相 F有界多相 オーバーロード 型クラス 単純な有界多相 注意:この図はMECEではありません 具象型に固有の振る舞いを実装 する 型をパラメータとして扱う (Generics) 型の親子関係を利用して具象型を共通型 として扱う ↓型パラメータ指定、型セット ←Goでは構造的に実現される。具象  型をinterfaceとして扱うイメージ ←Goではできない 相互再帰的有界多相 ←Goではinterfaceが暗黙的に適用される  ため、厳密な意味での型クラスは  存在しない 多重有界多相 インターフェースの合成
  6. どう変わったのか Go1.26リリースノートより: Besides making type constraints more powerful, this change

    also simplifies the spec rules for type parameters ever so slightly. 訳:この変更により、型制約がより強力になる だけでなく、型パラメータの仕様ルールも 若干簡素化されます。 出所:Go1.26 Release Notes
  7. 型セットとの比較 type Number interface { ~int | ~int64 | ~float64

    } func algo[T Number](x, y T) T { return x + y } type Adder[A Adder[A]] interface { Add(A) A } func algo[A Adder[A]](x, y A) A { return x.Add(y) } 型セットを使った書き方 ↑ algo関数で他の型も扱いたい  場合はここに追加する ↑ このインターフェースを満たす メソッドを実装すればOK ↑ Numberインターフェースの型セットに+演算子  を サポートしていない型を追加すると、ここで  コンパイルエラーになる。その場合は分岐が必要 ↑ 逆に、intなどのAddメソッドを  持たない型は使用できない 再帰的型制約を使った書き方
  8. ユースケース例 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  9. ユースケース例 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  10. Fluent interfaceの例 出所:mattn/go126-generics-example type Promise[ T any, P Promise[T, P]

    ] interface { Then(func(T) (T, error)) P Catch(func(error) (T, error)) P Await() (T, error) } p := NewPromise(func() (int, error) {...} p.Then(func(v int) (int, error) {... }).Then(func(v int) (int, error) {... }).Catch(func(err error) (int, error) {... }).Await() Promiseパターンの実装。具象型を維持しながらメソッドチェーンする振る舞いを定義できる
  11. ユースケース例 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  12. 相互再帰的なデータ構造の例(グラフ) type Node[N Node[N, E], E Edge[E, N]] interface {

    ID() string Edges() []E } type Edge[E Edge[E, N], N Node[N, E]] interface { From() N To() N Weight() float64 } Go Playground (AIに書いてもらったダイクストラ法 ) cities := []*City{tokyo, osaka, nagoya, fukuoka} dist := ShortestPath[*City, *Road](tokyo, cities) for _, city := range []*City{osaka, nagoya, fukuoka} { fmt.Printf(“ Tokyo -> %s: %.0f km..) } === Shortest Path from Tokyo (km) === Tokyo -> Osaka: 513 km Tokyo -> Nagoya: 366 km Tokyo -> Fukuoka: 1067 km
  13. どういう時に使うのか 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  14. Java標準のComparableインターフェース GoのComparable型制約(==, !=を使える)と違い、「同じ型同士で比較できる」という制約 public interface Comparable<T> { public int compareTo(T

    o); } public final class Collections { ...(中略) public static <T extends Comparable<? super T>> void sort(List<T> list) { list.sort(null); } } 出所:OpenJDK Comparable, util/Collections
  15. Goで「同じ型同士を比較するinterface」を定義する場合 type Comparable[T Comparable[T]] interface { CompareTo(other T) int }

    func Compare[T Comparable[T]](a, b T) int { return a.CompareTo(b) } 組み込みのcomparable型制約よりも型安全かつ厳密な制約を定義できる Go Playground
  16. 再帰的型制約を使わない場合と何が違うのか type Comparable[T any] interface { CompareTo(other T) int }

    func (o Apple) CompareTo(other Orange) int { return o.Value < other.Value } 同じメソッドを実装する違う型を複数種類扱う関数が書ける(それはそう)
  17. ユースケース例 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  18. 同じ型同士でしか計算できない定義型を作る type Unit[T any] interface { Add(other T) T }

    type Meter float64 func (m Meter) Add(other Meter) Meter { return m + other } type Foot float64 func (f Feet) Add(other Feet) Feet { ... func main() { meters := []Meter{1.5, 2.0, 3.5} totalMeter := Sum(meters) feet := []Foot{10.0, 5.5} totalFoot := Sum(feet) // コンパイルエラーの例:単位が混ざったスライスを 引数に渡すことはできない。 // mixed := []any{Meter(1.0), Foot(1.0)} // Sum(mixed) ... Go Playground
  19. ユースケース例 使用例: • Fluent interface(自己型を返しメソッドチェーンする設計パターン) ◦ ビルダーパターン、ORM、リポジトリの汎用的な関数など • 相互再帰的なデータ構造の操作 ◦

    グラフ構造、ステートマシンなど • 「自分と同じ型としか計算・比較できない」という制約 ◦ 順序比較やソートなどの比較処理の一般化 ◦ 計算のロジックは共通だが、単位が違うもの同士を混ぜてはいけない場合 ▪ 通貨、長さなどの単位が切り替わる場合など ◦ オブジェクトの複製や合成を行い、自己型を返すメソッド
  20. Grafana Labsのプロダクトにおけるk-wayマージの例 出所:GopherCon 2023: Blazing Fast Merge with Loser Trees

    - Bryan Boreham ↑ interfaceを使いたかったけど、  Generics型を使えなかったから諦めた例
  21. 2 5 4 1 6 8 2 1 6 複数のストリームを順序を保ってマージするアルゴリズム

    1 最小ヒープ (木) アウトプット 参考:k-wayマージとは 1がヒープから抜けたので、次 にヒープに入る ストリーム (Sequence)
  22. Valueインターフェイスを実装するノードの実装案 現状 type Tree[E any, S Sequence] struct { …

    less func(E, E) bool nodes []node[E, S] } type node[E any, S Sequence] struct { … value E items S // リーフノードのみ } 改善案 type Tree[E Value[E], S Sequence[E]] struct { nodes []node[E, S] } type Value[T Value[T]] interface { Less(T) bool } type MyNode {...} func (m MyNode) Less(other MyNode) bool {...} 出所:grafana/loki pkg/util/loser/tree.go Go Playground
  23. 参考資料 • Go 1.26 Release Notes: Changes to the language

    • Issue #75883 spec: remove cycle restriction for type parameters : 型パラメータの循環を制 限していた型チェッカーの実装を削除した時のイシュー。これによって Generics型で再帰的型制約が使えるようになっ た。 • Featherweight Go(2020): Go言語のジェネリクスの設計を書いた論文 • F-Bounded Polymorphism for Object-Oriented Programming(1989) • Wikipedia ポリモーフィズム • mattn/go126-generics-example: Generics型に再帰的型制約を使ったサンプルコード。 「オブジェクトの複 製」ユースケースもあります • GopherCon 2023: Blazing Fast Merge with Loser Trees - Bryan Boreham ◦ ブログ:The loser tree data structure: How to optimize merges and make your programs run faster • martinfowler.com: Fluent Interface(2005) • プログラマが知るべき97のこと: 【62】プリミティブ型よりドメイン固有の型を
  24. Genericsのユースケース Genericsを使うと良い状況 • 言語定義のコンテナ型(スライス、マップ、チャンネル)を使用する操作 • 汎用データ構造(連結リストなど、言語に組み込まれていないもの) • 異なる型で共通のメソッドを実装する必要があり、異なる型の実装が全て同じよう に見える場合 Genericsを使うべきではない状況

    • ある型の値のメソッドを呼び出すだけで済む場合はinterface型を使用する • メソッドの実装が型ごとに異なる場合は、interface型を使用する • メソッドを持たない型で操作をサポートする必要がある場合(例:encoding/jsonパッ ケージ) 出所:When To Use Generics (The Go Blog)
  25. 型理論における多相の種類 ←[T any] 多相 アドホック多相 パラメトリック多相 サブタイプ多相 無限パラメトリック多相 有界パラメトリック多相 F有界多相

    オーバーロード 型クラス 単純な有界多相 注意:この図はMECEではありません 具象型に固有の振る舞いを実装する 型をパラメータとして扱う (Generics) 型の親子関係を利用して具象型を共通型 として扱う ↓型パラメータ指定、型セット ←Goでは構造的に実現される。具象  型をinterfaceとして扱うイメージ ←Goではできない 相互再帰的有界多相 ←Goではinterfaceが暗黙的に適用される  ため、厳密な意味での型クラスは  存在しない 多重有界多相 インターフェースの合成 Generics型の再帰的型制約 Goではあまりできない