ポインターレシーバー

Set インターフェースを例で使用してみましょう。シーケンス内の重複要素を削除する関数を考えてみます:

// Unique は入力シーケンスから重複要素を削除し、各要素の最初のインスタンスのみを生成します。
func Unique[E comparable](input iter.Seq[E]) iter.Seq[E] {
    return func(yield func(E) bool) {
        seen := make(map[E]bool)
        for v := range input {
            if seen[v] {
                continue
            }
            if !yield(v) {
                return
            }
            seen[v] = true
        }
    }
}

これは map[E]boolE 要素のシンプルなセットとして使用します。結果として、comparable で等価演算子が組み込まれている型でのみ動作します。これを任意の型に一般化したい場合は、ジェネリックセットに置き換える必要があります:

// Unique は入力シーケンスから重複要素を削除し、各要素の最初のインスタンスのみを生成します。
func Unique[E any](input iter.Seq[E]) iter.Seq[E] {
    return func(yield func(E) bool) {
        var seen Set[E]
        for v := range input {
            if seen.Has(v) {
                continue
            }
            if !yield(v) {
                return
            }
            seen.Insert(v)
        }
    }
}

しかし、これは動作しません。Set[E] はインターフェース型で、seen 変数は nil に初期化されます。Set[E] インターフェースの具象実装を使用する必要があります。しかし、この記事で見てきたように、any 要素型で動作する一般的なセット実装はありません。

使用できる具象実装を、追加の型パラメータとしてユーザーに提供してもらう必要があります:

// Unique は入力シーケンスから重複要素を削除し、各要素の最初のインスタンスのみを生成します。
func Unique[E any, S Set[E]](input iter.Seq[E]) iter.Seq[E] {
    return func(yield func(E) bool) {
        var seen S
        for v := range input {
            if seen.Has(v) {
                continue
            }
            if !yield(v) {
                return
            }
            seen.Insert(v)
        }
    }
}

しかし、これをセット実装でインスタンス化すると、別の問題に遭遇します:

// OrderedSet[E] does not satisfy Set[E] (method All has pointer receiver)
Unique[E, OrderedSet[E]](slices.Values(s))
// panic: invalid memory address or nil pointer dereference
Unique[E, *OrderedSet[E]](slices.Values(s))

最初の問題はエラーメッセージから明らかです:型制約は S の型引数が Set[E] インターフェースを実装する必要があると言っています。そして、OrderedSet のメソッドがポインターレシーバーを使用するため、型引数もポインター型でなければなりません。

それを試すと、2番目の問題に遭遇します。これは実装で変数を宣言することに起因します:

var seen S

S*OrderedSet[E] の場合、変数は以前と同様に nil で初期化されます。seen.Insert の呼び出しはパニックを起こします。

ポインター型しかない場合、値型の有効な変数を取得できません。値型しかない場合、ポインターメソッドを呼び出すことはできません。結果として、値型_と_ポインター型の両方が必要です。そのため、新しい制約 PtrToSet を持つ追加の型パラメータ PS を導入する必要があります:

// PtrToSet は Set[E] インターフェースを実装するポインター型によって実装されます。
type PtrToSet[S, E any] interface {
    *S
    Set[E]
}

// Unique は入力シーケンスから重複要素を削除し、各要素の最初のインスタンスのみを生成します。
func Unique[E, S any, PS PtrToSet[S, E]](input iter.Seq[E]) iter.Seq[E] {
    return func(yield func(E) bool) {
        // PS に変換します。なぜなら、それだけがメソッドを持つことが制約されているからです。
        // PS の型セットは *S のみを含むため、変換は許可されます。
        seen := PS(new(S))
        for v := range input {
            if seen.Has(v) {
                continue
            }
            if !yield(v) {
                return
            }
            seen.Insert(v)
        }
    }
}

ここでのトリックは、PtrToSet インターフェースの追加型パラメータを介した関数シグネチャでの2つの型パラメータの接続です。S 自体は制約されていませんが、PS は型 *S を持ち、必要なメソッドを持つ必要があります。つまり、効果的に S をいくつかのメソッドを持つように制限していますが、それらのメソッドはポインターレシーバーを使用する必要があります。

この種の制約を持つ関数の定義には追加の型パラメータが必要ですが、重要なことに、これを使用するコードは複雑になりません:この追加の型パラメータが型パラメータリストの最後にある限り、推論できます:

// 3番目の型引数は *OrderedSet[int] に推論されます
Unique[int, OrderedSet[int]](slices.Values(s))

これは一般的なパターンであり、覚えておく価値があります:他の人の作品で遭遇するときや、自分の作品で使用したいときのためです。

func SomeFunction[T any, PT interface{ *T; SomeMethods }]()

一方がもう一方へのポインターとして制約される2つの型パラメータがある場合、制約は関連するメソッドがポインターレシーバーを使用することを保証します。