英文:
Mapping of different types with generics
问题
如何使用一个通用映射函数(Map2Vehicle)将两种不同类型(Car、Bicycle)映射到一个Vehicle类型。由于代码是自动生成的,Car和Bicycle不能更改。
错误信息显示:
have GetWheels() CarWheels
want GetWheels() wheels
cmd/tmp/tmp.go:9:19: *Bicycle does not satisfy vehicle[wheels] (wrong type for method GetWheels)
have GetWheels() BicycleWheels
want GetWheels() wheels
期望的输出是:
MyCar with 4 wheels MyBicycle with 2 wheels
有什么办法可以解决Golang泛型的问题吗?
英文:
How to map 2 different types (Car, Bicycle) with one generic mapping function (Map2Vehicle) to a Vehicle type. Car and Bicycle cannot be changed, because it's auto generated code.
package main
import "fmt"
func main() {
c := Car{Name: "MyCar", Wheels: CarWheels{Count: 4}}
b := Bicycle{Name: "MyBicycle", Wheels: BicycleWheels{Count: 2}}
v1 := Map2Vehicle(&c)
v2 := Map2Vehicle(&b)
fmt.Println(v1.Name, v2.Name)
}
// ---------------- Car
type Car struct {
Name string
Wheels CarWheels
}
type CarWheels struct {
Count int
}
func (c *Car) GetName() string { return c.Name }
func (c *Car) GetWheels() CarWheels { return c.Wheels }
func (w *CarWheels) GetCount() int { return w.Count }
// ---------------- Bicycle
type Bicycle struct {
Name string
Wheels BicycleWheels
}
type BicycleWheels struct {
Count int
}
func (b *Bicycle) GetName() string { return b.Name }
func (b *Bicycle) GetWheels() BicycleWheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int { return w.Count }
// ---------------- Vehicle
type wheels interface {
GetCount() int
}
type vehicle[T wheels] interface {
GetName() string
GetWheels() T
}
type Vehicle struct {
Name string
}
func Map2Vehicle[T vehicle[wheels]](v T) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
}
}
Error: The code does not work:
cmd/tmp/tmp.go:8:19: *Car does not satisfy vehicle[wheels] (wrong type for method GetWheels)
have GetWheels() CarWheels
want GetWheels() wheels
cmd/tmp/tmp.go:9:19: *Bicycle does not satisfy vehicle[wheels] (wrong type for method GetWheels)
have GetWheels() BicycleWheels
want GetWheels() wheels
Expected output:
MyCar with 4 wheels MyBicycle with 2 wheels
Any ideas how to solve the problem with golang generics?
答案1
得分: 2
首先,CarWheels
和BicycleWheels
没有实现wheels
接口。这是因为你的GetCount
方法使用指针接收器:
func (w *CarWheels) GetCount() int { return w.Count }
func (w *BicycleWheels) GetCount() int { return w.Count }
因此,*CarWheels
和*BicycleWheels
(指向结构体的指针)实现了wheels
接口,但结构体本身没有实现。我建议移除指针接收器,因为这些结构体非常小(实际上只是int
),没有必要使用指针来“包装”它们(这样只会增加开销):
func (w CarWheels) GetCount() int { return w.Count }
func (w BicycleWheels) GetCount() int { return w.Count }
另外,如果你的实际结构体较大,你当然也可以让GetWheels
方法返回指向轮子的指针,而不是轮子本身。
其次,你的函数签名是func Map2Vehicle[T vehicle[wheels]](v T) *Vehicle
。这意味着v
必须是一个具有返回类型为wheels
的vehicle
,而不是任何实现wheels
接口的返回类型。解决这个问题的方法是让GetWheels
函数返回抽象的wheels
类型,而不是具体的类型:
func (c *Car) GetWheels() wheels { return c.Wheels }
func (b *Bicycle) GetWheels() wheels { return b.Wheels }
如果你这样做,你的代码将按预期运行。
另外,如果你想保留具体的类型,你将不得不将轮子作为函数的第二个泛型参数:
func Map2Vehicle[W wheels, T vehicle[W]](v T) *Vehicle
Go 无法推断出W
(似乎泛型类型推断不能嵌套),所以你现在必须在调用时显式指定轮子的类型(它仍然可以推断出车辆的类型):
v1 := Map2Vehicle[CarWheels](&c)
v2 := Map2Vehicle[BicycleWheels](&b)
如果我要重构这段代码,我会完全消除泛型,让GetWheels
方法返回wheels
接口类型;然后Map2Vehicle
可以在任何vehicle
上操作,而vehicle
不需要是泛型的:
type vehicle interface {
GetName() string
GetWheels() wheels
}
func Map2Vehicle(v vehicle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
}
}
英文:
First off: CarWheels
and BicycleWheels
do not implement the wheels
interface. This is because your GetCount
methods use pointer receivers:
func (w *CarWheels) GetCount() int { return w.Count }
func (w *BicycleWheels) GetCount() int { return w.Count }
thus *CarWheels
and *BicycleWheels
(pointers to structs) implement the wheels
interface, but the structs themselves do not. I'd simply remove the pointer receivers since these structs are very small (effectively just int
s) and there's no need to "box" them using pointers (doing so would just incur overhead):
func (w CarWheels) GetCount() int { return w.Count }
func (w BicycleWheels) GetCount() int { return w.Count }
Alternatively, if your real structs are larger, you could of course also make your GetWheels
methods return pointers to the wheels rather than the wheels themselves.
Second: Your function has a signature of func Map2Vehicle[T vehicle[wheels]](v T) *Vehicle
. This means v
must be a vehicle
where the GetWheels
method has return type wheels
, not any return type implementing wheels
. The solution to this issue is to make GetWheels
functions return the abstract wheels
type rather than the specific types:
func (c *Car) GetWheels() wheels { return c.Wheels }
func (b *Bicycle) GetWheels() wheels { return b.Wheels }
If you do this, your code will run as expected.
Alternatively, if you want to keep the specific types, you'll have to make the wheels a second generic parameter of the function:
func Map2Vehicle[W wheels, T vehicle[W]](v T) *Vehicle
Go will be unable to infer W
(it seems generic type inference doesn't nest), so you have to make the type of wheels explicit at the call site now (it can still infer the type of the vehicle however):
v1 := Map2Vehicle[CarWheels](&c)
v2 := Map2Vehicle[BicycleWheels](&b)
If I were to refactor this, I would eliminate the generics entirely by having the GetWheels
method return the wheels
interface type; then Map2Vehicle
can just operate on any vehicle
, and vehicle
doesn't need to be generic:
type vehicle interface {
GetName() string
GetWheels() wheels
}
func Map2Vehicle(v vehicle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
}
}
答案2
得分: 0
为什么不创建两个不同类型的值,一个是Car(汽车),一个是Bicycle(自行车),然后使用Map2Vehicle
函数将每个值映射到Vehicle(车辆)类型?
Playground链接:https://play.golang.com/p/lPFoFEsjIeD
package main
import "fmt"
func main() {
c := Car{Name: "MyCar", Wheels: CarWheels{Count: 4}}
b := Bicycle{Name: "MyBicycle", Wheels: BicycleWheels{Count: 2}}
v1 := Map2Car(c)
v2 := Map2Bicycle(b)
fmt.Println(v1, v2)
}
// ---------------- Car
type Car struct {
Name string
Wheels CarWheels
}
type CarWheels struct {
Count int
}
func (c *Car) GetName() string { return c.Name }
func (c *Car) GetWheels() CarWheels { return c.Wheels }
func (w *CarWheels) GetCount() int { return w.Count }
// ---------------- Bicycle
type Bicycle struct {
Name string
Wheels BicycleWheels
}
type BicycleWheels struct {
Count int
}
func (b *Bicycle) GetName() string { return b.Name }
func (b *Bicycle) GetWheels() BicycleWheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int { return w.Count }
// ---------------- Vehicle
type Vehicle struct {
Name string
}
func Map2Car(c Car) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", c.GetName(), c.GetWheels().GetCount()),
}
}
func Map2Bicycle(b Bicycle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", b.GetName(), b.GetWheels().GetCount()),
}
}
英文:
Why not create two values of different types, a Car and a Bicycle, then use the Map2Vehicle
function to map each value to a Vehicle type?
Playground Link : https://play.golang.com/p/lPFoFEsjIeD
package main
import "fmt"
func main() {
c := Car{Name: "MyCar", Wheels: CarWheels{Count: 4}}
b := Bicycle{Name: "MyBicycle", Wheels: BicycleWheels{Count: 2}}
v1 := Map2Car(c)
v2 := Map2Bicycle(b)
fmt.Println(v1, v2)
}
// ---------------- Car
type Car struct {
Name string
Wheels CarWheels
}
type CarWheels struct {
Count int
}
func (c *Car) GetName() string { return c.Name }
func (c *Car) GetWheels() CarWheels { return c.Wheels }
func (w *CarWheels) GetCount() int { return w.Count }
// ---------------- Bicycle
type Bicycle struct {
Name string
Wheels BicycleWheels
}
type BicycleWheels struct {
Count int
}
func (b *Bicycle) GetName() string { return b.Name }
func (b *Bicycle) GetWheels() BicycleWheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int { return w.Count }
// ---------------- Vehicle
type Vehicle struct {
Name string
}
func Map2Car(c Car) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", c.GetName(), c.GetWheels()),
}
}
func Map2Bicycle(b Bicycle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", b.GetName(), b.GetWheels()),
}
}
答案3
得分: 0
你正在尝试在Go语言中使用泛型将不同类型(Car
和Bicycle
)映射到一个公共的Vehicle
类型。然而,你遇到了一个错误,因为Car
和Bicycle
类型对于GetWheels()
方法有不同的实现。
为了解决这个问题,你可以修改代码,使用接口而不是具体类型来表示Car
和Bicycle
结构体中的Wheels
字段。通过这样做,你可以定义一个满足wheels
接口的GetWheels()
的单一实现,适用于CarWheels
和BicycleWheels
。以下是你的代码的更新版本:
package main
import "fmt"
// ------- Car
type Car struct {
Name string
Wheels wheels // 使用接口而不是具体类型
}
type CarWheels struct {
Count int
}
func (c *Car) GetName() string { return c.Name }
func (c *Car) GetWheels() wheels { return c.Wheels }
func (w *CarWheels) GetCount() int { return w.Count }
// ------- Bicycle
type Bicycle struct {
Name string
Wheels wheels // 使用接口而不是具体类型
}
type BicycleWheels struct {
Count int
}
func (b *Bicycle) GetName() string { return b.Name }
func (b *Bicycle) GetWheels() wheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int { return w.Count }
// ------- 公共接口 -------
type wheels interface {
GetCount() int
}
type vehicle interface {
GetName() string
GetWheels() wheels
}
// ------- Vehicle
type Vehicle struct {
Name string
}
func Map2Vehicle(v vehicle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
}
}
func main() {
c := Car{Name: "MyCar", Wheels: &CarWheels{Count: 4}} // 传递一个CarWheels的指针
b := Bicycle{Name: "MyBicycle", Wheels: &BicycleWheels{Count: 2}} // 传递一个BicycleWheels的指针
v1 := Map2Vehicle(&c)
v2 := Map2Vehicle(&b)
fmt.Println(v1.Name, v2.Name)
}
现在,代码应该可以编译并产生你期望的输出:
MyCar with 4 wheels MyBicycle with 2 wheels
英文:
you're trying to use generics in Go to map different types (Car
and Bicycle
) to a common Vehicle
type. However, you're running into an error because the types Car
and Bicycle
have different implementations for the GetWheels()
method.
To solve this problem, you can modify your code to use an interface instead of a concrete type for the Wheels
field in both Car
and Bicycle
structs. By doing so, you can define a single implementation of GetWheels()
that satisfies the wheels
interface for both CarWheels
and BicycleWheels
. Here's an updated version of your code:
package main
import "fmt"
// ------- Car
type Car struct {
Name string
Wheels wheels // Use interface instead of concrete type
}
type CarWheels struct {
Count int
}
func (c *Car) GetName() string { return c.Name }
func (c *Car) GetWheels() wheels { return c.Wheels }
func (w *CarWheels) GetCount() int { return w.Count }
// ------- Bicycle
type Bicycle struct {
Name string
Wheels wheels // Use interface instead of concrete type
}
type BicycleWheels struct {
Count int
}
func (b *Bicycle) GetName() string { return b.Name }
func (b *Bicycle) GetWheels() wheels { return b.Wheels }
func (w *BicycleWheels) GetCount() int { return w.Count }
// ------- Common Interfaces -------
type wheels interface {
GetCount() int
}
type vehicle interface {
GetName() string
GetWheels() wheels
}
// ------- Vehicle
type Vehicle struct {
Name string
}
func Map2Vehicle(v vehicle) *Vehicle {
return &Vehicle{
Name: fmt.Sprintf("%s with %d wheels", v.GetName(), v.GetWheels().GetCount()),
}
}
func main() {
c := Car{Name: "MyCar", Wheels: &CarWheels{Count: 4}} // Pass a pointer to CarWheels
b := Bicycle{Name: "MyBicycle", Wheels: &BicycleWheels{Count: 2}} // Pass a pointer to BicycleWheels
v1 := Map2Vehicle(&c)
v2 := Map2Vehicle(&b)
fmt.Println(v1.Name, v2.Name)
}
Now, the code should compile and produce the output you expect:
MyCar with 4 wheels MyBicycle with 2 wheels
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论