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

Generic Methods - 「Go には来ない」と言われ続けた機能は、 なぜ Go 1...

Generic Methods - 「Go には来ない」と言われ続けた機能は、 なぜ Go 1.27 に入ったのか

LOGOSWARE.go#1 Go1.27 リリースパーティでの発表資料

Avatar for Yasutomo Nakajima

Yasutomo Nakajima

August 25, 2026

Other Decks in Technology

Transcript

  1. Go FAQ — Why does Go not have generic methods?

    “we do not anticipate that Go will ever add generic methods” Go にジェネリックメソッドが入ることは、我々は想定していない → 2026年1月、この一文は撤回された ( golang/go#77273, griesemer )
  2. 反対の根拠は、13年間ほぼ同じだった • interface は暗黙的に満たされる。実装側に「実装します」の宣言がない • メソッドに型パラメータを許すなら、interface のメソッドにも許すことになる • だが interface

    越しの呼び出しは動的ディスパッチ • 実行時にどのインスタンス化が必要か、コンパイル時に知る術がない • “in nite possible instantiations” — 事前生成も辞書渡しも設計できない fi • ∴ ジェネリックメソッドは効率的に実装できない
  3. どういうことか • ジェネリック関数は、呼び出し側で型引数が見える • interface 越しの呼び出しは、中身の型が実行時に決まる(動的ディスパッチ) • interface の実体は「値 +

    メソッド表」— 1欄 = コンパイル済み関数1つのアドレス • 要る実体は型引数ごとに別物。しかも型は別パッケージから後からでも渡される type Mapper interface { Map[R any](f func(int) R) []R } // 仮に書けたとして var m Mapper = MySlice{} // 中身の型は実行時まで分からない m.Map[string](f) // 表のどの欄を引く? その実体は誰が生成した? ∴ 表に載せる関数を、コンパイル時に決められない
  4. 今回の提案は用語を分けること • concrete method — レシーバを持つ、interface でないメソッド • interface method

    — interface 内に書かれた「名前 + シグネチャ」 • concrete method のレシーバ型は、呼び出し側で静的に確定している • ∴ コンパイラは実体をジェネリック関数呼び出しに書き換えられる • interface を満せないことは “an orthogonal aspect”(直交する別問題) 実装できないのは interface method の側だった
  5. A change of view. “any parameterized method can be implemented

    as a parameterized function.” これを書いていたのは、2021年の Type Parameters Proposal 自身 • ジェネリックメソッド = レシーバを持つジェネリック関数 • 新しい実装技術は何も要らなかった。要ったのは問題の切り分け
  6. メソッドが型パラメータを持てる type S struct{ … } func (*S) m[P any](x

    P) { … } // ← メソッド自身の型パラメータ var s S s.m[int](42) // 明示的にインスタンス化 s.m(x) // 型引数は実引数から推論
  7. レシーバの型パラメータと共存する type G[P any] struct{ … } func (*G[P]) m[Q

    any](x Q) { … } g := &G[string]{} g.m(42) // P=string は g から / Q=int は実引数から • スコープはメソッド名の直後から本体末尾まで(関数と同じ) • 制約(constraint)からはレシーバの型パラメータを参照できる
  8. 仕様変更は驚くほど小さい FunctionDecl = "func" [ Receiver ] identifier [ TypeParameters

    ] Signature [ FunctionBody ] . Operand = Literal | OperandName | "(" Expression ")" . PrimaryExpr = … | PrimaryExpr TypeArgs • 関数とメソッドの生成規則を、1本に統合できる(Receiver が任意になる) • TypeArgs を Operand から PrimaryExpr に移す • T[int] と a[i] は構文上完全に同一で、パーサはすでにこう読んでいた • 重複を仕様に残してあるのは “for the sake of documentation”
  9. ジェネリックメソッドは interface を満たさない type I interface{ m(string) } type H

    struct{ … } func (H) m[P any](P) { … } var _ I = h // ✗ H.m は m[P any](P) — m(string) と一致しない • interface method に型パラメータは書けない(変更なし) • 同名でもシグネチャが違うので、常に別物として扱われる
  10. 意図的に入れなかったもの • (*Reader).Read[byte] のようなメソッド単体のインスタンス化 は無い • re ect からは名前でもインデックスでも到達できない •

    未インスタンス化のジェネリック関数と同じ • レシーバの型引数はメソッドより先に確定していなければなら fl ない
  11. interface メソッドの一般化 ≒ 高階型(HKT) • interface method に型パラメータを許す = 型コンストラクタを型変数にする

    • つまり Functor / Monad が書けるようになる、といういつもの話 type Functor[F ??, A any] interface { Map[B any](func(A) B) F[B] } // F は「型を取って型を返す」変数 = 高階の型変数 ここで Go の型推論の前提が壊れる
  12. 型推論のパターンが発散する • Go の推論は「実引数の型と単一化して型引数を一意に解く」設計 • 型コンストラクタ変数が入ると、高階の単一化になる F[B] ≡ List[int] を解く

    解1: F = List, B = int 解2: F = λX. List[int], B = 何でもよい • 解が一意に定まらない ∴ 推論は発散し、一般には決定不能 • ただし、将来の可能性を閉じてはいない(条件はゼロコストな解)
  13. まとめ • 不可能の正体は、2つの別問題をともに束ねていたこと • concrete method と interface method を分けた瞬間、片方は実装可能に

    なった • 増えたのは表現力。減ったのは回避策(#49085 が置き換わる) • 残った課題は interface method の一般化 • 言語設計は、機能を追加する作業より、制約を分解する作業なのかも
  14. EOF