使用泛型实现比较功能的Go语言结构体

3

考虑以下代码片段,适用于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 没有定义相等运算符。然而,我找不到如何定义它们的方法。
问题:这种创建可比较结构体的方法是否被禁止了?为什么?或者是文档尚未编写?
1个回答

5
约束条件comparable是预声明的,并由语言规范支持。您不能“手动”使类型实现它。文档在规范中可用(在Type Constraints下):

预声明的接口类型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 {
    // test equality in a way that makes sense for this type
}

值得一提的是,有一种方法可以使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。

1
谢谢,这个答案非常清晰易懂。但是这让我感到有些不安,因为这些关于Go泛型的初步实验似乎就此停滞了... - HJLebbink
这个例子说明了我使用泛型的主要动机。而且我正在努力应对轻微的失望。或者还有另一种方法可以创建可调整大小的可比较结构体(例如切片)吗? - HJLebbink
@HJLebbink,目前我不知道有更清晰的设计。人们可能会尝试在联合或类型断言中使用“comparable”,但这是不可能的。如果一个类型从设计上来说不可比较,那么它必须以不同的方式处理,例如通过实现Equal方法等...请注意,数组是可比较的,但长度是固定的。 - blackgreen
@HJLebbink 如果你将字段声明为指针,例如 data_ *[]T,则可以使 Vector[T] 可比较。根据上面引用的规范,指针是可比较的。但我不确定是否更可取,因为 1)初始化和使用变得更加繁琐和冗长,2)指针比较测试内存地址是否相同,因此 T 是另一个 Vector[T] 实例时,x==e 基本上检查这两个结构体是否持有完全相同的切片指针。经验可能有所不同。示例 https://gotipplay.golang.org/p/zSx2mjLmptz - blackgreen
@blackgreen 可能值得提及通常的 NaN 注意事项:https://go.dev/play/p/SlZCtn6PEUe - jub0bs
@jub0bs,感谢您的提醒,当然提一下也无妨;不过在这个特定的问答中,OP的用例是使用另一个Vector来实例化Vector,以便x == e可以比较两个Vector[T]。 OP最初失败是因为该结构体具有切片字段,因此采用了明显不好的技巧将该字段更改为切片指针。 - blackgreen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接