如何实现通用接口?

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

How to implement generic interfaces?

问题

我刚刚看到Go语言在最新版本中引入了泛型,并且我正在尝试创建一个小项目来理解它的工作原理。除了非常简单的函数现在可以是泛型之外,我似乎无法理解它的工作方式。我想要能够做这样的事情:

type Dao[RT any] interface {
	FindOne(id string) *RT
}

type MyDao struct {
}

type ReturnType struct {
	id int
}

func (m *MyDao) FindOne(id string) *ReturnType {
	panic("implement me")
}

// 这应该是什么样子?

func NewMyDao() *Dao[ReturnType] {
	return &MyDao[ReturnType]{}
}

这样做可能吗?我似乎没有以那种方式实现接口,我尝试了很多相同的组合。

有没有一种实现泛型接口的方法?如果没有,那么替代方案只能返回interface{}类型吗?

英文:

I've just seen Go has incorporated generics in its latest release, and I'm trying to create a small project to understand how it works. I don't seem to figure out how it works apart from very simple functions being now generic. I'd like to be able to do things like this:

type Dao[RT any] interface {
	FindOne(id string) *RT
}

type MyDao struct {
}

type ReturnType struct {
	id int
}

func (m *MyDao) FindOne(id string) *ReturnType {
	panic("implement me")
}

// how should this look like?

func NewMyDao() *Dao[ReturnType] {
	return &MyDao[ReturnType]{}
}

Is that even possible? I don't seem to be implementing the interface that way, and I've tried many combinations of the same.

Is there a way to implement a generic interface? If not, is the alternative only to return the interface{} type?

答案1

得分: 51

类型实际上并不实现通用接口,它们实现的是通用接口的实例化。你不能使用未实例化的通用类型(包括接口)。从这里开始,它就像是在泛型之前的Go中一样,包括具有指针接收器的方法的区别。

因此,有助于思考如果使用具体类型重写使用类型参数的方法会是什么样子。

让我们考虑一个通用接口和一些类型:

type Getter[T any] interface {
    Get() T
}

type MyStruct struct {
    Val string
}

有几种可能的情况

具有具体类型参数的接口

实例化为Getter[string],由具有Get() string方法的类型实现

// 实现 Getter[string]
func (m MyStruct) Get() string {
   return m.Val
}

// ok
func foo() Getter[string] {
    return MyStruct{}
}

将类型参数作为类型参数的接口

具有类型参数的函数可以使用它们来实例化通用类型,例如Getter[T]。实现者必须具有Get() T方法。为了使其有效,它们也是通用的,并使用相同的类型参数进行实例化:

因此,即使Tstring,这也不会编译

// Getter[T] 需要具有 `Get() T` 方法的实现者
func bar[T any]() Getter[T] {
    return MyStruct{} // 不会编译,即使 T 是 string
}

使MyStruct也成为参数化的工作方式:

type MyStruct[T any] struct {
    Val T
}

func (m MyStruct[T]) Get() T {
    return m.Val
}

func bar[T any]() Getter[T] {
    return MyStruct[T]{} // ok
}

具有通用实现者的具体接口

让我们反转前面的情况。我们保持参数化的MyStruct[T any],但现在接口不是参数化的:

type Getter interface {
    Get() string
}

在这种情况下,只有当MyStruct使用必要的具体类型进行实例化时,它才实现Getter

// Getter 需要方法 `Get() string`
func baz() Getter {
    return MyStruct[string]{} // 使用 string 进行实例化,ok
    // return MyStruct[int]{} // 使用其他类型进行实例化,不会编译
}

指针接收器

这遵循与上述相同的规则,但需要实例化指针类型,与通常情况下一样:

// 指针接收器,实现 Getter[string]
func (m *MyStruct) Get() string {
   return m.Val
}

func foo() Getter[string] {
    return &MyStruct{} // ok
    // return MyStruct{} // 不会实现
}

如果MyStruct是通用的,情况也是一样的。

// 参数化的指针接收器
func (m *MyStruct[T]) Get() T {
   return m.Val
}

func foo() Getter[string] {
    return &MyStruct[string]{} // ok
}

<hr>

因此,在你的情况下,将类型参数替换为具体类型的思维练习表明,Dao[ReturnType]具有方法FindOne(id string) *ReturnType。实现此方法的类型是*MyDao(指针接收器),因此:

func NewMyDao() Dao[ReturnType] {
    return &MyDao{}
}
英文:

Types don't actually implement generic interfaces, they implement instantiations of generic interfaces. You can't use a generic type (including interfaces) without instantiation. From there, it is just like pre-generics Go, including the difference between methods with pointer receiver.

Therefore it is helpful to think what the methods that use type parameters would look like if you rewrote them with concrete types.

Let's consider a generic interface and some type:

type Getter[T any] interface {
    Get() T
}

type MyStruct struct {
    Val string
}

There's a few possible cases

Interface with concrete type argument

Instantiate as Getter[string], implemented by types with method Get() string

// implements Getter[string]
func (m MyStruct) Get() string {
   return m.Val
}

// ok
func foo() Getter[string] {
    return MyStruct{}
}

Interface with type parameter as type argument

Functions that have type parameters may use those to instantiate generic types, e.g. Getter[T]. Implementors must have exactly the Get() T method. For that to be valid, they are also generic and instantiated with the same type parameter:

So this doesn't compile even if T is string

// Getter[T] literally needs implementors with `Get() T` method
func bar[T any]() Getter[T] {
    return MyStruct{} // doesn&#39;t compile, even if T is string
}

Making MyStruct also parametrized works:

type MyStruct[T any] struct {
    Val T
}

func (m MyStruct[T]) Get() T {
    return m.Val
}

func bar[T any]() Getter[T] {
    return MyStruct[T]{} // ok
}

Concrete interface with generic implementor

Let's reverse the previous cases. We keep the parametrized MyStruct[T any] but now the interface is not parametrized:

type Getter interface {
    Get() string
}

In this case, MyStruct implements Getter only when it is instantiated with the necessary concrete type:

// Getter requires method `Get() string`
func baz() Getter {
    return MyStruct[string]{} // instantiate with string, ok
    // return MyStruct[int]{} // instantiate with something else, doesn&#39;t compile
}

Pointer receivers

This follows the same rules as above, but requires instantiating pointer types, as usual:

// pointer receiver, implements Getter[string]
func (m *MyStruct) Get() string {
   return m.Val
}

func foo() Getter[string] {
    return &amp;MyStruct{} // ok
    // return MyStruct{} // doesn&#39;t implement
}

and it is the same if MyStruct is generic.

// parametrized pointer receiver
func (m *MyStruct[T]) Get() T {
   return m.Val
}

func foo() Getter[string] {
    return &amp;MyStruct[string]{} // ok
}

<hr>

So in your case, the mental exercise of replacing the type params with concrete types gives that Dao[ReturnType] has method FindOne(id string) *ReturnType. The type that implements this method is *MyDao (pointer receiver), therefore:

func NewMyDao() Dao[ReturnType] {
    return &amp;MyDao{}
}

答案2

得分: 3

类型*MyDao实现了接口Dao[ReturnType]。因此,函数应该如下所示:

func NewMyDao() Dao[ReturnType] {
    return &MyDao{}
}

请注意,返回类型是泛型接口的实例,返回值只是*MyDao类型的实例。

英文:

The type *MyDao implements the interface Dao[ReturnType]. Thus, the function should look like:

func NewMyDao() Dao[ReturnType] {
    return &amp;MyDao{}
}

Note that the return type is an instance of the generic interface, and the returned value is simply an instance of the *MyDao type.

huangapple
  • 本文由 发表于 2022年4月28日 03:46:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/72034479.html
匿名

发表评论

匿名网友

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

确定