在Go约束中实现异构接收器

zbwhf8kr  于 12个月前  发布在  Go
关注(0)|答案(2)|浏览(132)

上下文

这个问题是关于定义规范的,适当的,或最佳实践的方法来定义指针和非指针接收器之间混合的类型的类型约束。为了清晰起见,我定义了几个术语:

  • 约束:作为类型参数提供的接口类型。限制类型参数以满足接口的所有元素。
  • 类型集:接口中所有元素的类型集的交集;非接口类型具有包含它们自己的类型自身。
  • 近似元素:在Go语言中表示为~,这个操作符实际上意味着“底层类型为X的任何类型”,前提是“X”自己的底层类型是它本身。

问题

我通过例子来阐明这个问题。考虑以下情况:

// Type A and pointer receiver
type A struct {
  s string
}
func (a *A) String() string {
  return a.s
}

// Type constraint C
type C interface {
  A
  String() string
}

func show [T C] (x T) {
    fmt.Println(x.String())
}

func main() {
  a := A{s:"a"}
  show(a)
}

字符串
尝试运行此代码将失败,并出现以下错误:
A不满足C(方法String没有指针接收器)
显然,指针-接收器是在指向类型A的 * pointer * 上定义的,而不是A本身。这可以通过简单地将C约束修改为在类型*A上定义,并使用适当的引用调用show来解决:

// Type constraint C
type C interface {
  *A
  String() string
}

// Adjustment to main
func main() {
  // ...
  show(&a)
}


到目前为止一切都很好,但是如何解决A类型同时有 * 指针和非指针类型接收器的情况呢?

// Type A, normal receiver, and pointer receiver
type A struct {
  s string
}
func (a A) Show() {
  fmt.Println(a.s)
}
func (a *A) String() string {
  return a.s
}

// Type constraint C: ??? 
type C interface {
  A
  Show()
  String() string
}

func show1 [T C] (x T) {
    fmt.Println(x.String())
}

func main() {
  a := A{s:"a"}
  show(a)
}


约束C要求类型C的所有示例都实现Show * 和 * String。但是Show是在A上定义的,String是在*A上定义的。现在验证这些变得更加困难

尝试解决方案

我试图用各种解决方案来解决这个问题,我将其划分如下:
1.按类型近似
1.按工会类型
1.按类型参数

近似元素

第一种方法可能是使用一个 *approximation元素 *。然而,这是用来描述其 * 底层 * 类型为T的所有类型的集合,并且指向T的指针似乎不被算作派生类型:

// Type constraint C
type C interface {
  ~struct{s string} // You may not use A here, for it's underlying type is not itself :) 
  String() string
  Show()
}

类型联合

另一种方法是尝试使用类型联合。然而,这已经被认为是有缺陷的,因为Go泛型要求类型集的每个成员都实现它。因此,String不能自动解析为*AShow不能自动解析为A

// Type constraint C
type C interface {
  A | *A   // A does not satisfy C (method String has pointer receiver) 
  String() string
  Show()
}

类型参数调整

最后一种方法是废除单一的单一约束。毕竟,它不能同时满足普通和指针接收方法。然而,试图内联两个不同的约束本身就会遇到问题。

func show [P interface{ String() string}, T interface{Show()}] (x T) {
    x.Show()
    p := P(&x) // Cannot convert &x (value of type T) to type P
    fmt.Println(p.String())
}


似乎我不能正确定义P,这样我就可以说服编译器它是T的指针。尽管*T实现了对P的约束。
所以我的问题变成了:如何在Go语言中为具有多个接收器的类型定义约束?必须定义不同的约束吗?或者可以在一个中执行所有这些约束吗?是否有一些语法技巧我错过了将类型连接到它们的指针?

更新

根据一些反馈,我用一个真实的用例更新了我的问题,试图让MVE更容易理解:

  • 假设我想为一个数据库写一个接口,这个数据库有很多表。
  • 每个表都有一定数量的具有不同字段的列。
  • 在Go语言中,我们可以用一个struct来表示每个字段。现在,为了使我们的接口更灵活,我们可以编写一个泛型接口,在调用时返回行类型T
  • 但是,我们还希望行类型T为我们的接口提供查询字符串。因此,我们在*T上定义了Query() string方法。

我们想在指针接收器上这样做的原因是因为这是结构体的推荐做法:
如果接收器是一个大的结构体或数组,指针接收器更有效。多大是大?假设它相当于将其所有元素作为参数传递给方法。如果感觉太大,它对接收器来说也太大。
Go Wiki - Receiver Type
请注意,当使用非泛型代码时,您可以在类型上调用值和指针接收器方法,而不管它是否是指针。由于这不适用于开箱即用的泛型,这就是我写我的问题的原因之一。

axr492tv

axr492tv1#

也许这个例子与您的真实的问题相比过于简单,但是为什么泛型感觉是解决这个问题的正确方法呢?
简单的界面是解决问题的最佳方式:

package main

import "fmt"

type A struct {
    s string
}

func (a A) String() string {
    return a.s
}

func (a *A) Show() {
}

type StringShower interface {
    fmt.Stringer
    Show()
}

func show(x StringShower) {
    fmt.Println(x.String())
    x.Show()
}

func main() {
    a := A{s: "a"}
    show(&a)
}

字符串
如果是因为你需要返回调用者提供的值或对它做一些事情,你可以把这个接口作为泛型使用。它可以很好地与异构的接收器一起工作:

func show[T StringShower](x T) T {
    fmt.Println(x.String())
    return x
}

a = &A{s: "a"}
a = show(a)

zbsbpyhn

zbsbpyhn2#

在我的实验中,我发现以下是 * 一个可能的解决方案 *。我在这里基本上是在做的是定义两个约束,一个用于普通类型,一个用于指针类型:

*普通类型接口T interface { Show() }:定义一个方法Show,直接对T进行操作
*指针类型接口P interface { *T; String () string }:定义一个方法String,该方法操作*T,即T的指针。

把这些放在一起,我得到:

func show [P interface {*T; String() string}, T interface{Show()}] (x T) {
    x.Show()
    p := P(&x)
    fmt.Println(p.String())
}

字符串
然而,要使用指针接收器,你仍然需要进行强制转换。我发现强制转换不是很优雅。请参阅此解决方案in this gist的完整源代码。我有兴趣找到更好的方法来做到这一点,所以将等待其他建议/解释。

相关问题