ポインターレシーバー
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 SS が *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つの型パラメータがある場合、制約は関連するメソッドがポインターレシーバーを使用することを保証します。