Go泛型:如何声明与另一个类型参数兼容的类型参数

inkz8wg9  于 2023-02-17  发布在  Go
关注(0)|答案(1)|浏览(253)

我正在寻找一种方法来声明Go语言泛型约束中类型参数之间的类型兼容性。
更具体地说,我需要说明一些类型T与另一个类型U兼容,例如,T是一个指向实现接口U的结构体的指针。
下面是我想完成的一个具体例子:

  • 注意:请不要用实现“数组前置”的替代方法来回答。我只是将它作为我希望解决的问题的一个具体应用。专注于具体的示例会偏离主题。*
func Prepend[T any](array []T, values ...T) []T {
  if len(values) < 1 { return array }

  result := make([]T, len(values) + len(array))

  copy(result, values)
  copy(result[len(values):], array)

  return result
}

可以调用上面的函数将给定类型T的元素追加到相同类型的数组中,因此the code below可以正常工作:

type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }

func grow(array []*Foo) []*Foo {
  return Prepend(array, &Foo{x: len(array)})
}

如果数组类型与要添加的元素不同(例如,由元素类型实现的接口),则the code fails to compile(如预期)与type *Foo of &Foo{…} does not match inferred type Base for T

type Base interface { fmt.Stringer }
type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }

func grow(array []Base) []Base {
  return Prepend(array, &Foo{x: len(array)})
}

直观的解决方案是修改Prepend的类型参数,使arrayvalues具有不同但兼容的类型,这是我不知道如何在Go语言中表达的部分。
例如,the code below不能正常工作(正如预期的那样),因为arrayvalues的类型是相互独立的;类似的代码可以用于C++模板,因为兼容性是在模板示例化之后**验证的(类似于duck类型),Go编译器给出错误invalid argument: arguments to copy result (variable of type []A) and values (variable of type []T) have different element types A and T

func Prepend[A any, T any](array []A, values ...T) []A {
  if len(values) < 1 { return array }

  result := make([]A, len(values) + len(array))

  copy(result, values)
  copy(result[len(values):], array)

  return result
}

I've tried making类型TA兼容,约束为~A,但是Go语言不喜欢类型参数作为约束的类型,给出了错误type in term ~A cannot be a type parameter

func Prepend[A any, T ~A](array []A, values ...T) []A {

在不诉诸反射的情况下,将这种类型兼容性声明为泛型约束的正确方法是什么?

de90aj5v

de90aj5v1#

这是Go语言类型参数推理的一个局限性,类型参数推理是指在没有显式定义类型参数的情况下,自动插入类型参数的系统,尝试显式地插入类型参数,你会发现它是有效的,例如:

// This works.
func grow(array []Base) []Base {
  return Prepend[Base](array, &Foo{x: len(array)})
}

您还可以尝试将*Foo值显式转换为Base接口。例如:

// This works too.
func grow(array []Base) []Base {
  return Prepend(array, Base(&Foo{x: len(array)}))
}

解释

首先,您应该记住,类型参数的“正确”使用是始终显式包含它们。省略类型参数列表的选项被认为是“最好拥有”,但不打算涵盖所有用例。
来自博客文章An Introduction To Generics

类型推断在实践中

类型推理工作的确切细节是复杂的,但使用它并不复杂:类型推理要么成功要么失败。2如果成功,类型参数可以省略,调用泛型函数看起来和调用普通函数没有什么不同。3如果类型推理失败,编译器将给予一个错误信息,在这种情况下,我们可以只提供必要的类型参数。
在向语言中添加类型推断时,我们尝试在推断能力和复杂性之间取得平衡。我们希望确保当编译器推断类型时,这些类型永远不会令人惊讶。我们尝试小心地犯错误,在未能推断出类型的一侧而不是推断出错误的类型的一侧。我们可能没有完全正确。我们可能会在未来的版本中继续完善它。其结果将是更多的程序可以不用显式类型参数来编写。今天不需要类型参数的程序明天也不需要。
换句话说,类型推断可能会随着时间的推移而改进,但您应该预料到它是有限的。
在这种情况下:

// This works.
func grow(array []*Foo) []*Foo {
  return Prepend(array, &Foo{x: len(array)})
}

对于编译器来说,通过替换T = *Foo来匹配[]*Foo*Foo的参数类型与模式[]T...T的匹配是相对简单的。
那么,为什么你最初给出的简单解决方案行不通呢?

// Why does this not work?
func grow(array []Base) []Base {
  return Prepend(array, &Foo{x: len(array)})
}

要使[]Base*Foo与模式[]T...T匹配,仅仅替换T = *FooT = Base没有提供明显的匹配。您必须应用*Foo可赋值给类型Base的规则来查看T = Base是否工作。显然,推理系统不“Don“不要多花一点力气去弄清楚这一点,所以在这里失败了。

相关问题