英文:
go struct with generics implementing comparable
问题
考虑以下用于 go1.18beta2 版本的 Linux/amd64 的代码片段:
type Vector[T comparable] struct {
data_ []T
}
func (v *Vector[T]) Contains(e T) bool {
for _, x := range v.data_ {
if x == e {
return true
}
}
return false
}
func TestVector(t *testing.T) {
v2 := Vector[Vector[int]]{}
}
这段代码无法编译,并报错:"Vector[int] does not implement comparable",这是因为 Vector
没有定义相等运算符。然而,我找不到如何定义它们的方法。
问题:这种创建可比较的结构体的方法是否不允许?为什么?或者是因为文档尚未编写?
英文:
Consider the following code snippet for version go1.18beta2 linux/amd64
type Vector[T comparable] struct {
data_ []T
}
func (v *Vector[T]) Contains(e T) bool {
for _, x := range v.data_ {
if x == e {
return true
}
}
return false
}
func TestVector(t *testing.T) {
v2 := Vector[Vector[int]]{}
}
This does not compile and gives error: “Vector[int] does not implement comparable”
simply because Vector
has no equality operators defined. However, I cannot find how to define them.
Question: Is this approach of creating a comparable struct not allowed, and why; or is the documentation not written yet?
答案1
得分: 4
comparable
约束是预先声明的,并且由语言规范支持。你不能“手动”使一个类型实现它。文档可以在规范中找到(在类型约束下):
预声明的接口类型
comparable
表示所有具体(非接口)类型的集合,这些类型是可比较的。具体而言,如果类型T满足以下条件,则类型T实现了comparable
:
T
不是接口类型,并且T
支持操作符==和!=;或者T
是接口类型,并且T
的类型集合中的每个类型都实现了comparable
。
你的类型Vector[T comparable]
不满足上述任何条件。它不是接口类型,而且它也不支持相等操作,因为它的一个字段data_ []T
由于是切片而不可比较 — 即使元素类型受到comparable
的约束。
comparable
约束的目的实际上只是允许使用==
和!=
操作符编写通用代码。如果一个类型从设计上来说是不可比较的,你就不能编写这样的代码。即使Vector
没有类型参数,这个道理也是成立的。
如果你的目标是实例化Vector[Vector[T]]
并允许在Vector[T]
的实例之间进行相等性测试,你可能希望添加一个Equal
方法来处理这个特定的用例 — 只有使用与接收者相同的类型参数实例化的向量才会被允许:
func (v *Vector[T]) Equal(e Vector[T]) bool {
// 根据该类型的语义进行相等性测试
}
值得一提的是,有一种方法可以使Vector[T comparable]
本身可比较,即将data_
字段更改为指向切片的指针:
type Vector[T comparable] struct {
data_ *[]T
}
现在使用Vector[Vector[int]]
进行实例化是可以编译的。然而,除了使用结构体字面量进行初始化非常麻烦之外(playground),它还带来了指针比较的所有注意事项。具体来说:
如果两个指针值指向同一个变量,或者两个指针值都为nil,则它们是相等的。指向不同的零大小变量的指针可能相等,也可能不相等。
现在比较x == e
测试的是x
和e
中data_
字段中存储的内存地址是否相同。这可能会扭曲比较两个Vector[T]
实例的语义 —— 如果它们持有对同一个切片的引用,那么说两个向量实例相等是否正确?也许。这取决于你的程序希望做出的假设。个人而言,我认为这实际上并不比拥有一个单独的Equal
方法和/或重新设计你的数据类型更好,但通常情况下,因人而异。
还要注意,如果你实例化为Vector[float64]
并比较NaN
值,比较结果将为false。
英文:
The constraint comparable
is predeclared and supported by the language specifications. You can't "manually" make a type implement it. The documentation is available in the specs (under Type Constraints):
> The predeclared interface type comparable denotes the set of all concrete (non-interface) types that are comparable. Specifically, a type T implements comparable if:
>
> - T
is not an interface type and T
supports the operations == and !=; or
> - T
is an interface type and each type in T
's type set implements comparable.
Your type Vector[T comparable]
doesn't meet any of those conditions. It is not an interface type, and it does not otherwise support the equality operations, because one of its fields data_ []T
is not comparable due to being a slice — even if element type is constrained by comparable
.
The purpose of the comparable
constraint is really just to allow writing generic code with ==
and !=
operators. If a type is not comparable by design, you can't write such code. That would be true even if Vector
didn't have a type parameter.
If your goal is instantiating Vector[Vector[T]]
and allow equality tests between instances of Vector[T]
, you might want to add an Equal
method that takes care of this specific use case — only vectors instantiated with the same type parameter as the receiver will be allowed:
func (v *Vector[T]) Equal(e Vector[T]) bool {
// test equality in a way that makes sense for this type
}
<hr>
Worth mentioning that there is a way to make Vector[T comparable]
comparable itself, i.e. change the data_
field to be a pointer-to-slice:
type Vector[T comparable] struct {
data_ *[]T
}
Now instantiation with Vector[Vector[int]]
compiles. However, beside being very cumbersome to initialize with struct literals (playground), it comes with all caveats of pointer comparison. More specifically:
> Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.
Now the comparison x == e
tests that the memory address stored in the data_
field in x
and e
is the same. This might skew the semantics of comparing two Vector[T]
instances — is it correct to say that two vector instances are equal if they hold a reference to the same slice? Maybe. It depends on the assumptions your program wants to make. Personally, I don't think this is actually better than having a separate Equal
method and/or redesigning your data types, but as usual, YMMV.
Note also that if you instantiate as Vector[float64]
and compare NaN
values, the comparison will be false.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论