英文:
Go generics: self-referring interface constraint
问题
我有几种自定义类型需要以相同的方式处理。泛型似乎是一个完美的选择。在处理过程中,我需要调用类型实例的方法,这些方法返回相同类型的不同实例,然后我需要调用返回实例的方法,但我无法使其正常工作。为了说明问题,我制作了一个简化的类型集合和一个展示问题的过程的示例。
以下是一个没有使用泛型的工作示例,展示了类型(Circle
和Square
)以及我将尝试抽象为泛型函数的过程(.Bigger().Smaller()
)(在线演示):
package main
import (
"fmt"
)
type Circle struct{ r float64 }
func NewCircle(r float64) *Circle { return &Circle{r: r} }
func (c *Circle) Radius() float64 { return c.r }
func (c *Circle) Bigger() *Circle { return &Circle{r: c.r + 1} }
func (c *Circle) Smaller() *Circle { return &Circle{r: c.r - 1} }
type Square struct{ s float64 }
func NewSquare(s float64) *Square { return &Square{s: s} }
func (s *Square) Side() float64 { return s.s }
func (s1 *Square) Bigger() *Square { return &Square{s: s1.s + 1} }
func (s1 *Square) Smaller() *Square { return &Square{s: s1.s - 1} }
func main() {
fmt.Println(NewCircle(3).Bigger().Smaller().Radius()) // 输出 3
fmt.Println(NewSquare(6).Bigger().Smaller().Side()) // 输出 6
}
首先,我需要定义一个类型约束:
type ShapeType interface {
*Circle | *Square
}
我将向 process
方法传递一个 ShapeType
,并且我需要能够调用 ShapeType
实例的方法,因此我需要定义另一个类型约束,指定可以在 ShapeType
上调用的方法:
type Shape[ST ShapeType] interface {
Bigger() ST
Smaller() ST
}
有了这些,我可以编写一个 process
方法(在线演示):
func process[ST ShapeType](s Shape[ST]) ST {
return s.Bigger().Smaller()
}
然而,这无法编译,因为 s.Bigger()
的返回值是 ST
,而不是 Shape[ST]
,因此 Go 不知道它可以在 s.Bigger()
的返回值上调用 Smaller()
。按照 Go 的说法:
> s.Bigger().Smaller undefined (type ST has no field or method Smaller)
如果 Bigger()
和 Smaller()
不返回其接收者类型的实例,我可以这样写:
type Shape interface {
*Circle | *Square
Bigger()
Smaller()
}
func process[S Shape](x S) S {
x.Bigger().Smaller()
return x // 为了示例,我猜我们甚至不需要返回 x
}
但是,我需要这样写:
type Shape interface {
*Circle | *Square
Bigger() Shape
Smaller() Shape
}
而且似乎 Go 不喜欢自引用的类型约束。
如果可以将具体类型断言/转换为其符合的接口类型,那么我可以使其工作,但似乎不可能做到这一点(在线演示):
func process[ST ShapeType](s Shape[ST]) ST {
s1 := s.Bigger()
s2 := s1.(Shape[ST]) // 这里 Go 不开心
return s2.Smaller()
}
对此,Go 会报错:
> cannot use type assertion on type parameter value s1 (variable of type ST constrained by ShapeType)
我不知道还能尝试什么。
是否可能使用泛型处理这些类型?如果可以,应该如何处理?
英文:
I have a couple custom types that I need to process in the same way. It seems like a perfect use for generics. In the process, I need to call methods on instances of the types, and those methods return different instances of the same types, and then I need to call methods on those returned instances, which I can't get to work. For the purpose of this question, I've fabricated a much simpler set of types and a process that exemplifies the problem I'm running in to.
Here's a working example without generics that shows the types (Circle
and Square
), and a process (.Bigger().Smaller()
) I'll be trying to abstract into a generic function later (online demo):
package main
import (
"fmt"
)
type Circle struct{ r float64 }
func NewCircle(r float64) *Circle { return &Circle{r: r} }
func (c *Circle) Radius() float64 { return c.r }
func (c *Circle) Bigger() *Circle { return &Circle{r: c.r + 1} }
func (c *Circle) Smaller() *Circle { return &Circle{r: c.r - 1} }
type Square struct{ s float64 }
func NewSquare(s float64) *Square { return &Square{s: s} }
func (s *Square) Side() float64 { return s.s }
func (s1 *Square) Bigger() *Square { return &Square{s: s1.s + 1} }
func (s1 *Square) Smaller() *Square { return &Square{s: s1.s - 1} }
func main() {
fmt.Println(NewCircle(3).Bigger().Smaller().Radius()) // prints 3
fmt.Println(NewSquare(6).Bigger().Smaller().Side()) // prints 6
}
The first thing I do to make a generic function is to define a type constraint:
type ShapeType interface {
*Circle | *Square
}
I'll be passing a ShapeType
to a process
method, and I need to be able to call methods on the ShapeType
instance, so I need to define another type constraint which specifies the methods that can be called on a ShapeType
:
type Shape[ST ShapeType] interface {
Bigger() ST
Smaller() ST
}
With these, I can write a process
method (online demo):
func process[ST ShapeType](s Shape[ST]) ST {
return s.Bigger().Smaller()
}
This fails to compile however, as the return value of s.Bigger()
is an ST
, not a Shape[ST]
, so go doesn't know that it can then call Smaller()
on the return value of s.Bigger()
. In go's words:
> s.Bigger().Smaller undefined (type ST has no field or method Smaller)
If Bigger()
and Smaller()
didn't return instances of their receiver types, I could write:
type Shape interface {
*Circle | *Square
Bigger()
Smaller()
}
func process[S Shape](x S) S {
x.Bigger().Smaller()
return x // I guess we wouldn't even have to return x, but just for example's sake
}
Instead I would need to write:
type Shape interface {
*Circle | *Square
Bigger() Shape
Smaller() Shape
}
and it appears go doesn't like self-referential type constraints.
If it were possible to assert/convert a concrete type to an interface it conforms to, then I could make it work, but it doesn't appear to be possible to do that (online demo):
func process[ST ShapeType](s Shape[ST]) ST {
s1 := s.Bigger()
s2 := s1.(Shape[ST]) // go is not happy here
return s2.Smaller()
}
For this, go says:
> cannot use type assertion on type parameter value s1 (variable of type ST constrained by ShapeType)
I don't know what else to try.
Is it possible to work with these kinds of types with generics? If so, how?
答案1
得分: 1
将你尝试的两个接口合并在一起:
type Shape[ST any] interface {
*Circle | *Square
Bigger() ST
Smaller() ST
}
然后使用类型参数本身来实例化process
的约束:
func process[ST Shape[ST]](s ST) ST {
return s.Bigger().Smaller()
}
- 将
*Circle | *Square
添加到Shape[ST any]
中意味着只有这两种类型才能实现该接口。 - 然后在方法签名中使用类型参数,比如
Bigger() ST
,意味着传递的任何类型都有一个返回自身的方法。
如果你想将ShapeType
保持为一个独立的接口,你可以将Shape
写成:
type Shape[ST any] interface {
ShapeType
Bigger() ST
Smaller() ST
}
你也可以使用类型推断来使用process
方法,没有任何问题:
func main() {
c1 := NewCircle(3)
c2 := process(c1)
fmt.Println(c2.Radius()) // 正如预期的那样打印3
fmt.Printf("%T\n", c2) // *main.Circle
s1 := NewSquare(6)
s2 := process(s1)
fmt.Println(s2.Side()) // 正如预期的那样打印6
fmt.Printf("%T\n", s2) // *main.Square
}
最终的 playground:https://go.dev/play/p/_mR4wkxXupH
英文:
Combine your two attempted interfaces together:
type Shape[ST any] interface {
*Circle | *Square
Bigger() ST
Smaller() ST
}
And then instantiate the constraint of process
with the type parameter itself:
func process[ST Shape[ST]](s ST) ST {
return s.Bigger().Smaller()
}
- Adding the union element
*Circle | *Square
intoShape[ST any]
means that only those two types will be able to implement the interface - Then using the type parameter in the method signature, like
Bigger() ST
, means that whichever type is passed has a method that returns itself.
If you want to keep ShapeType
as a separated interface, you can write Shape
as:
type Shape[ST any] interface {
ShapeType
Bigger() ST
Smaller() ST
}
You can also use process
method with type inference, without any issue:
func main() {
c1 := NewCircle(3)
c2 := process(c1)
fmt.Println(c2.Radius()) // prints 3 as expected
fmt.Printf("%T\n", c2) // *main.Circle
s1 := NewSquare(6)
s2 := process(s1)
fmt.Println(s2.Side()) // prints 6 as expected
fmt.Printf("%T\n", s2) // *main.Square
}
Final playground: https://go.dev/play/p/_mR4wkxXupH
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论