(2023/9/13)コードが分かりづらかったので、全面的に書き直しました
(x1 *X) Add(x2 *X) *X
というメソッドを持つ型(X)(y1 *Y) Add(y2 *Y) *Y
というメソッドを持つ型(Y)
これを等価に扱いたい。
引数がその固定の型であれば普通の interface で済むところだが、引数が interface 型とかではなくレシーバー1と同じ型とするとなると、途端に難しくなる。
これを、ジェネリクスの二枚重ねと interface の組み合わせでなんとかしてみた。
まずは等価に扱いたい型二つの定義は次のとおり。X は整数型、Y は実数型のラッパー。Add というメソッドがあるが、パラメータはレシーバーと同じ型(fmt.Print で表示できるように String() string も一応実装)
package main import ( "fmt" ) // 等価に扱いたい型:その1:X(整数) type X struct { value int } func (x1 *X) Add(x2 *X) *X { return &X{value: x1.value + x2.value} } func (x *X) String() string { return fmt.Sprintf("%d", x.value) } // 等価に扱いたい型:その2:Y(実数) type Y struct { value float64 } func (y1 *Y) Add(y2 *Y) *Y { return &Y{value: y1.value + y2.value} } func (y *Y) String() string { return fmt.Sprintf("%f", y.value) }
次に足し算が出来る型の interface を定義する。この時、パラメータはレシーバーと同じ型にしないといけないので、そこでジェネリクスを使う
// 足し算ができる型の generics interface 定義 type Adder[T any] interface { Add(t T) T }
これを使う例として { x+=x ; x+=x ; x+=x }
的な汎用関数を書いてみる。ジェネリクスなinterfaceをパラメータにとるので、この関数自体もジェネリクス関数になってしまう(これはしゃーない)
しかし、よくコンパイル通ったな…
// { x+=x ; x+=x ; x+=x } を実行する汎用関数 func three[A Adder[A]](x A) A { for i := 0; i < 3; i++ { x = x.Add(x) } return x }
テスト用の main は次のとおり
// テスト用メイン func main() { x := &X{value: 2} fmt.Println(three(x)) y := &Y{value: 3} fmt.Println(three(y)) }
結果は一応期待どおり。
$ go run main.go 16 24.000000
func three[A Adder[A]](x A) A
という関数仕様がすごくみっともないし、曲芸じみているので、本番コードではあまり使うべきではないが…
フルソースは以下のとおり
- いわゆる self とか this↩