ポインターレシーバーに制約すべきか?
この時点で、かなり圧倒されているかもしれません。これはかなり複雑で、すべてのGoプログラマーがこの関数シグネチャで何が起こっているかを理解することを期待するのは無理があるように思えます。また、APIにさらに多くの名前を導入する必要がありました。最初にGoにジェネリクスを追加することに対して人々が警告したとき、これは彼らが心配していたことの一つでした。
したがって、これらの問題に巻き込まれることがあれば、一歩下がって考えることが価値があります。問題を別の方法で考えることで、この複雑さを避けることができることが多くあります。この例では、iter.Seq[E]
を受け取り、一意の要素を持つ iter.Seq[E]
を返す関数を構築しました。しかし、重複排除を行うには、一意の要素をセットに収集する必要がありました。そして、これには結果全体に対してスペースを割り当てる必要があるため、結果をストリームとして表現することから実際には恩恵を受けません。
この問題を再考すると、セットを通常のインターフェース値として使用することで、追加の型パラメータを完全に避けることができます:
// InsertAll は seq からすべての一意の要素を set に追加します。
func InsertAll[E any](set Set[E], seq iter.Seq[E]) {
for v := range seq {
set.Insert(v)
}
}
Set
を普通のインターフェース型として使用することで、呼び出し元が具象実装の有効な値を提供する必要があることが明確になります。これは非常に一般的なパターンです。そして、iter.Seq[E]
が必要な場合は、set
で All()
を呼び出して取得できます。
これは呼び出し元にとって少し複雑になりますが、ポインターレシーバーへの制約に対する別の利点があります:map[E]bool
をシンプルなセット型の基礎として使用することから始めたことを覚えておいてください。その基礎で Set[E]
インターフェースを実装するのは簡単です:
type HashSet[E comparable] map[E]bool
func (s HashSet[E]) Insert(v E) { s[v] = true }
func (s HashSet[E]) Delete(v E) { delete(s, v) }
func (s HashSet[E]) Has(v E) bool { return s[v] }
func (s HashSet[E]) All() iter.Seq[E] { return maps.Keys(s) }
この実装はポインターレシーバーを使用しません。したがって、これは完全に有効ですが、ポインターレシーバーへの複雑な制約では使用できませんでした。しかし、InsertAll
バージョンでは正常に動作します。多くの制約と同様に、メソッドがポインターレシーバーを使用することを強制することは、多くの実用的なユースケースに対して実際には過度に制限的である可能性があります。