Goの進化:ジェネリックメソッドの提案

長年にわたり、Goコミュニティは言語の型システムにおける奇妙な制限に直面してきました。関数や型はジェネリックにできますが、メソッドはできません。複数の型を扱うメソッドが必要な場合、そのロジックをスタンドアロンのジェネリック関数に移動せざるを得ず、その結果、使い勝手の悪いAPIや「janky(ぎこちない)」なパッケージ構造を招くことがよくありました。

現在、新しい提案では、Goがメソッドをどのように扱うかに関する「見方の変化」が示されています。具体的なメソッドをインターフェースの実装者としての役割から切り離すことで、Goチームはついに言語にジェネリックメソッドをもたらそうとしています。

コアとなる問題:メソッド vs インターフェース

なぜ当初のジェネリックス実装からジェネリックメソッドが除外されたのかを理解するには、Goにおける具体的なメソッドとインターフェースの関係を理解する必要があります。歴史的に、メソッドの主な目的はインターフェースを満たすことでした。

もしGoが具体的なメソッドに型パラメータを持たせることを許可した場合(例:func (s *S) DoSomething[T any](val T))、論理的にはインターフェースもそれらを許可すべきということになります。しかし、ジェネリックなインターフェースメソッドを実装することは、大きな技術的課題です。Goは構造的部分型(型が暗黙的にインターフェースを実装する仕組み)を使用しているため、コンパイラは、インターフェースを介して実行時に呼び出される可能性のあるジェネリックメソッドの、考えられるすべてのインスタンス化をコンパイル時に知ることができません。

長い間、Go FAQに反映されている公式の立場は、このインターフェースのボトルネックのために、言語にジェネリックメソッドが追加されることはないかもしれない、というものでした。

「見方の変化」

新しい提案では、インターフェースを実装するかどうかにかかわらず、具体的なメソッドはそれ自体で有用であると主張しています。メソッドには以下の利点があります:

  1. より優れた組織化: ロジックを、それが操作するデータと直接関連付けることができます。
  2. 構文的な流動性: メソッドチェーン(x.a().b().c())を可能にし、これはネストされた関数呼び出し(c(b(a(x))))よりも読みやすくなります。

この提案は、実用的な妥協案を提示しています:ジェネリックな具体的なメソッドは許可するが、それらがインターフェースを満たすことは許可しない。

このルールに基づくと、インターフェースがメソッド m(string) を定義している場合、具体的なメソッド m[P any](P) はそれに一致しません。なぜなら、インターフェースのメソッドは型パラメータを持つことができないからです。これにより、Goチームは、ジェネリックなインターフェースのディスパッチという極めて複雑な問題を解決することなく、ジェネリックメソッドの構文的および組織的な利点を提供することができます。

技術的な実装と構文

提案されている変更は、構文的に最小限です。メソッド宣言は、関数宣言を反映するように更新されます:

MethodDecl = "func" Receiver MethodName [ TypeParameters ] Signature [ FunctionBody ]

実践的な例

基本的なジェネリックメソッド:

type S struct { ... }
func (*S) m[P any](x P) { ... }

var s S
s.m[int](42) // 明示的な型引数
s.m(x)       // 型推論

ジェネリックなレシーバを持つジェネリックメソッド:

type G[P any] struct{ ... }
func (*G[P]) m[Q any](x Q) { ... }

コンパイラへの影響

提案では、インターフェースではないレシーバを介したメソッド呼び出しは、コンパイル時に静的に解決できると述べています。概念的には、コンパイラはこれらの呼び出しをジェネリックな関数呼び出しに書き換えることができ、大きな実行時のオーバーヘッドを導入することなく実装が可能です。

コミュニティの反応:分かれる意見

この発表は、Goコミュニティからさまざまな反応を引き起こしており、これは、表現力を求める層と、Goの本来の哲学である極端なシンプルさを重視する層との間の広範な緊張を反映しています。

熱狂的な支持者

多くの開発者は、他の現代的な言語から移行してきた人々にとって、この重要な欠落を埋めるものであるとして、安堵しています。あるユーザーは、これが「奇妙でぎこちないパッケージAPI」を排除するのに役立つと指摘し、一方で、他のユーザーは、ついにモナドのような複雑なパターンを実装できることに興奮を表しています。

伝統主義者

逆に、一部の人々は、これをGoの核心的なアイデンティティからの逸脱であると見ています。批判的な人々は、ユーザーの期待を追いかけることは「機能の肥大化(feature creep)」を招き、このような複雑さの追加は言語のシンプルさを損なうものであると主張しています。

"Goにとって悲しい日です。博士号保持者が勝ち、シンプルさが死にました。"

結論

ジェネリックな具体的なメソッドを導入することで、Goは、理論的な純粋さと開発者の生産性の間のギャップを橋渡ししようとしています。ジェネリックなインターフェースの問題は解決しませんが、メソッドをコードを組織化するための手段として、メソッドの有用性は、すべてのメソッドがインターフェースの実装者である必要があるという必要性よりも勝ることが多いということを認めています。もし採用されれば、この変更は、開発者がジェネリックなロジックをパッケージレベルの関数へ戻すのではなく、型に所属する適切な場所に移動させるため、ライブラリのリファクタリングの波を引き起こすでしょう。

Sources