不同类型的映射与泛型

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

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

首先,CarWheelsBicycleWheels没有实现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必须是一个具有返回类型为wheelsvehicle,而不是任何实现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 ints) 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()),
}
}

output: 不同类型的映射与泛型

答案3

得分: 0

你正在尝试在Go语言中使用泛型将不同类型(CarBicycle)映射到一个公共的Vehicle类型。然而,你遇到了一个错误,因为CarBicycle类型对于GetWheels()方法有不同的实现。

为了解决这个问题,你可以修改代码,使用接口而不是具体类型来表示CarBicycle结构体中的Wheels字段。通过这样做,你可以定义一个满足wheels接口的GetWheels()的单一实现,适用于CarWheelsBicycleWheels。以下是你的代码的更新版本:

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

huangapple
  • 本文由 发表于 2023年7月23日 17:05:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76747420.html
匿名

发表评论

匿名网友

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

确定