使用泛型实现可比较的 Go 结构体。

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

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测试的是xedata_字段中存储的内存地址是否相同。这可能会扭曲比较两个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.

huangapple
  • 本文由 发表于 2022年2月10日 20:32:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/71065172.html
匿名

发表评论

匿名网友

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

确定