进一步约束 Golang 中的类型参数(实现具有 Contains 方法的通用列表)

huangapple go评论85阅读模式
英文:

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/ListList/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 or List/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"))

huangapple
  • 本文由 发表于 2023年6月25日 18:31:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76549966.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定