英文:
Further constraining a type parameter in Golang (implementing generic List with Contains method)
问题
假设我想编写一个通用的List
类型,其中包含一些有用的方法,例如:
type List[T any] []T
func (l *List[T]) Len() int
func (l *List[T]) Get(pos int) (t T, err error)
func (l *List[T]) Set(pos int, t T) error
func (l *List[T]) Append(t ...T)
func (l *List[T]) Insert(t T, pos int) error
func (l *List[T]) Remove(pos int) (t T, err error)
// 等等...
然而,还有其他一些有用的方法可能需要进一步约束列表的元素类型T
。例如,我们无法在这个List
类型上实现一个Contains
方法:
func (l *List[T]) Contains(t T) bool {
for _, s := range *l {
if s == t { // 编译错误:无效操作:s == t(类型不可比较)
return true
}
}
return false
}
只有当我们将List
声明为
type List[T comparable] []T
时,我们才能实现Contains
方法。
但是,这样就无法创建一个非可比较类型的List
。
在这里有没有办法兼顾两全呢?也就是说,是否可以有一个List[T]
,可以用于非可比较类型T
,但在T
可比较的情况下允许它具有Contains
方法?
我考虑过以下方法:
- 使用不同的类型(例如
UncomparableList
/List
或List
/ComparableList
) - 将
Contains
作为函数而不是方法
但我不太喜欢这两种方法。
英文:
Let's say I want to write a generic List
type with some useful methods, for example:
type List[T any] []T
func (l *List[T]) Len() int
func (l *List[T]) Get(pos int) (t T, err error)
func (l *List[T]) Set(pos int, t T) error
func (l *List[T]) Append(t ...T)
func (l *List[T]) Insert(t T, pos int) error
func (l *List[T]) Remove(pos int) (t T, err error)
// etc...
However, there are other useful methods that might require the list's element type T
to be further constrained. For example, we can't implement a Contains
method on this List
type:
func (l *List[T]) Contains(t T) bool {
for _, s := range *l {
if s == t { // compiler error: invalid operation: s == t (incomparable types in type set)
return true
}
}
return false
}
We can only implement Contains
if we declare List
as
type List[T comparable] []T
But then this makes it impossible to make a List
of a non-comparable type.
Is there a way to get the best of both worlds here? i.e. have a List[T]
that can be used for non-comparable types T
, but allow it to have a Contains
method in the case that T
is comparable?
I've thought of:
- using different types (e.g.
UncomparableList
/List
orList
/ComparableList
) - making
Contains
a function rather than a method
but I don't really like either of these.
答案1
得分: 1
Go语言没有泛型特性,所以我认为你无法完全按照这种方式实现它(虽然我不是泛型专家)。
我认为一个比较符合Go风格的方法是传入一个显式的比较器:
func (l *List[T]) Contains(t T, cmp func(T, T) bool) bool {
for _, s := range *l {
if cmp(s, t) {
return true
}
}
return false
}
然后你可以这样使用:
func main() {
list := List[int]([]int{1,2,3})
fmt.Println(list.Contains(2, func(a, b int) bool { return a == b })) // true
}
对于可比较的类型,你可以提供一个默认的比较器:
func Eq[T comparable](a, b T) bool {
return a == b
}
这样上面的代码就变成了:
func main() {
list := List[int]([]int{1,2,3})
fmt.Println(list.Contains(2, Eq[int])) // true
}
你还可以在List
类型中嵌入一个比较器,并将其默认值设为func(a, b T) bool { return false }
,并提供一个构造函数,可以将自定义的比较器传入其中。但这可能对你来说太隐式了。
英文:
Go doesn't have specialization so I don't think you can make it work exactly like this (not a generics expert, though).
I think a reasonably Go-ish way to go about this is to pass in an explicit comparator:
func (l *List[T]) Contains(t T, cmp func(T, T) bool) bool {
for _, s := range *l {
if cmp(s, t) {
return true
}
}
return false
}
Then you can do
func main() {
list := List[int]([]int{1,2,3})
fmt.Println(list.Contains(2, func(a, b int) bool { return a == b })) // true
}
For comparable types you could provide a default:
func Eq[T comparable](a, b T) bool {
return a == b
}
So the above becomes
func main() {
list := List[int]([]int{1,2,3})
fmt.Println(list.Contains(2, Eq[int]) // true
}
You could also embed a comparator inside the List
type and give it a default of func(a, b T) bool { return false }
and expose a constructor that you can pass a custom comparator into. But that might be too implicit for your liking.
答案2
得分: 0
回答一般问题:据我所知,Go语言不允许方法的集合依赖于类型参数。
如果你想添加额外的方法,你需要将原始类型嵌入到扩展类型中。(一般来说,这是在Go语言中实现类似继承的方式)。
type List[T any] []T
type ComparableList[T comparable] struct {
List[T]
}
然后ComparableList
继承了List
的所有方法,所以我们可以在ComparableList
上定义Contains
方法:
func (l *ComparableList[T]) Contains(t T) bool {
for _, s := range l.List {
if s == t {
return true
}
}
return false
}
为了能够使用Contains
方法,我们可以直接创建一个ComparableList
,或者将一个List
转换为ComparableList
。我们可以编写辅助函数来完成这两种操作:
func NewComparableList[T comparable]() ComparableList[T] {
return ComparableList[T]{}
}
func MakeComparable[T comparable](l List[T]) ComparableList[T] {
return ComparableList[T]{l}
}
英文:
To answer the general question: IINM, Go doesn't allow the set of methods to depend on the type parameter.
If you wanted to add extra methods, you would have to embed the original type in an extended type. (In general, this is the only way you can sort-of do inheritance in Go).
type List[T any] []T
type ComparableList[T comparable] struct {
List[T]
}
Then ComparableList
inherits all the methods of List
, so we can just define the Contains
method on ComparableList
:
func (l *ComparableList[T]) Contains(t T) bool {
for _, s := range l.List {
if s == t {
return true
}
}
return false
}
To be able to use the Contains
method, we either make a ComparableList
off the bat, or later convert a List
to a ComparableList
. We could write helper functions to do either of these:
func NewComparableList[T comparable]() ComparableList[T] {
return ComparableList[T]{}
}
func MakeComparable[T comparable](l List[T]) ComparableList[T] {
return ComparableList[T]{l}
}
答案3
得分: 0
受@isaactfa的答案的启发,我想到了另一种处理非comparable
类型的Contains
函数的方法。
基本上,将一个谓词 func(T) bool
传递给Contains
函数,这样Contains
函数就会检查列表是否包含满足谓词条件的元素。对于通常的情况,谓词将是 func(t T) bool { return t == something }
。
// Contains返回true,如果l包含满足谓词f的元素。
func (l *List[T]) Contains(f func(T) bool) bool {
for _, t := range *l {
if f(t) {
return true
}
}
return false
}
l := List[string]{}
l.Append("foo")
l.Contains(func(s string) bool { return s == "foo" }) // true
我甚至可能进一步将其转化为一个Find
函数:
// Find找到满足给定谓词f的第一个元素。
func (l *List[T]) Find(f func(T) bool) (pos int, t T, found bool) {
for i, t := range *l {
if f(t) {
return i, t, true
}
}
return
}
你还可以定义默认的谓词以方便使用:
func Is[T comparable](t T) func(T) bool {
return func(s T) bool { return s == t }
}
l.Contains(Is("foo"))
英文:
Inspired by @isaactfa's answer, I thought of another way to do Contains
for a non-comparable
type.
Essentially, pass in a predicate func(T) bool
to the Contains
function, so Contains
is checking if the list contains an element satisfying the predicate. For the usual case, the predicate will be func(t T) bool { return t == something }
.
// Contains returns true if l contains an element satisfying the predicate f.
func (l *List[T]) Contains(f func(T) bool) bool {
for _, t := range *l {
if f(t) {
return true
}
}
return false
}
l := List[string]{}
l.Append("foo")
l.Contains(func(s string) bool { return s == "foo" }) // true
I'd probably even go one further and turn this into a Find
function:
// Find finds the first element satisfying the given predicate f.
func (l *List[T]) Find(f func(T) bool) (pos int, t T, found bool) {
for i, t := range *l {
if f(t) {
return i, t, true
}
}
return
}
You could also define the default predicate for convenience:
func Is[T comparable](t T) func(T) bool {
return func(s T) bool { return s == t }
}
l.Contains(Is("foo"))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论