概要
- Go言語の メソッド に対する ジェネリック(型パラメータ) 導入提案
- インターフェースメソッド には影響せず、 具体型メソッド のみが対象
- シンタックス変更 は最小限で、既存コードと 後方互換性 を維持
- 呼び出し・型推論・メソッド式 も期待通り動作
- 実装・仕様変更 は比較的シンプルだが、エクスポートフォーマット更新が必要
Goにおけるジェネリックメソッド導入提案
- 具体型メソッド (receiver付き関数)に 型パラメータ 導入を提案
- 関数宣言 と同様に、メソッド宣言でも [TypeParameters] を許可
- 例:
func (s *S) m[P any](x P) { ... }のような宣言が可能 - インターフェースメソッド には型パラメータを導入しない方針
- 理由:Goのインターフェース実装は動的であり、どの型パラメータのインスタンスが必要かコンパイル時に決定できないため
- ジェネリックメソッド は インターフェースの実装要件 を満たさない
ジェネリックメソッドの意義と利点
- メソッド はインターフェース実装以外にも、型に紐づく関数として コード整理 や 可読性向上 に有用
- x.a().b().c() のような メソッドチェーン による直感的な記述が可能
- 関数型プログラミング 的な
c(b(a(x)))よりも自然な記述 - Goユーザーからの要望 が強く、既に多くの支持を集めている
提案される文法変更
- メソッド宣言 のシンタックスを以下のように変更
- 旧:
funcReceiver MethodName Signature [FunctionBody] - 新:
funcReceiver MethodName [TypeParameters] Signature [FunctionBody]
- 旧:
- 例:
func (r *Reader) Read[E any]([]E) (int, error) { ... } - 型パラメータのスコープ はメソッド名以降~メソッド本体終了まで
- メソッド式・メソッド値 もジェネリック関数として扱う
- 呼び出し時 は型パラメータを明示または推論で指定可能
実装例
- 具体型S に対しジェネリックメソッドmを定義
type S struct { ... }func (*S) m[P any](x P) { ... }
- 呼び出し例
s.m[int](42)// 明示的型指定s.m(x)// 引数xから型推論
- ジェネリック型G[P] にもメソッド宣言可能
func (*G[P]) m[Q any](x Q) { ... }
- インターフェースI の実装例
type I interface { m(string) }type G[P any] struct{ ... }func (G[P]) m(P) { ... }var g G[string]var _ I = g// G[string]による実装は有効type H struct{ ... }func (H) m[P any](P) { ... }var _ I = h// Hは実装不可(型パラメータが一致しないため)
仕様・リフレクション・制限
- インターフェースメソッド は非対応
- リフレクション(reflectパッケージ) 経由で未インスタンス化のジェネリックメソッドにはアクセス不可
- 既存のメソッド呼び出し・メソッド式 との互換性維持
実装・コンパイラ・仕様変更
- パーサ は既にジェネリックメソッド構文を許容(現状はエラーを出すだけ)
- 型チェッカー は制限解除などの調整が必要
- コンパイラバックエンド は、非インターフェースreceiver経由のメソッド呼び出しは静的に解決可能
- メソッド呼び出しを関数呼び出しへ変換
- ジェネリックメソッド呼び出しも対応
- エクスポート・インポートデータ形式 の更新が最も影響大
- 多数のツール・リポジトリで同期的な対応が必要
まとめ
- Go言語のメソッド文法拡張 による ジェネリックメソッド 導入提案
- インターフェースとの分離 で実装・仕様の複雑化を回避
- ユーザー利便性向上 と 後方互換性維持 を両立
- 実装コスト は限定的だが、エコシステム全体の調整が必要