標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

メソッドの引数がレシーバーと同じ型となっている型群を等価に扱いたい

(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 という関数仕様がすごくみっともないし、曲芸じみているので、本番コードではあまり使うべきではないが…

フルソースは以下のとおり


  1. いわゆる self とか this