ポインターレシーバー
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]bool
を E
要素のシンプルなセットとして使用します。結果として、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つの型パラメータがある場合、制約は関連するメソッドがポインターレシーバーを使用することを保証します。