泛型:使用派生类型传递映射
Generics: Pass map with derived types
在下面的例子中foo
和bar
基本上是同一个类型:map[uint32]string
.
然而 go1.18beta 抱怨说:M2 does not match map[K]V
.
是否有可能 equal
接受这两张地图?我是否需要更改 equal
的签名或地图本身的声明?
package main
import "fmt"
func equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func main() {
foo := map[uint32]string{
10: "bar",
}
bar := map[someNumericID]someStringID{
10: "bar",
}
if equal(foo, bar) == true {
fmt.Println("Maps are the same")
} else {
fmt.Println("Maps are not the same")
}
}
Is it even possible to get equal to accept both of these maps?
是的,但是您必须区分键和值类型,因为它们不相同。这就是固定函数的样子:
func equal[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
特别是,键和值类型参数都被限制为各自的近似元素 ~uint32
和 ~string
,以便允许转换 m2[K2(k)]
和 V2(v1)
函数体。这是比较不同类型但具有相同 底层 类型的值(包括地图索引)所必需的。
上述解决方案在地图类型上放弃了类型参数 M1
和 M2
— 由于似乎是编译器错误;有关详细信息,请参阅注释 — 但由于您实际上并未在函数体中使用这些类型,也未在 return 值中使用这些类型,因此 .
游乐场:https://gotipplay.golang.org/p/Y8C_8ilsXUg
如果您想了解第一个示例失败的原因,请查看此处的细分。语言规范中的相关段落是 Type inference.
- 在
equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2)
中,类型参数M1
和M2
具有相同的约束~map[K]V
。
- 当您在没有显式实例化的情况下调用函数时,编译器会尝试从提供的参数类型中推断类型参数。简而言之,它从
M1
推断出 K
和 V
,因此 equal(foo, bar)
其中 foo
是 map[uint32]string
结果是 K = uint32
和 V = string
.
- 那么实例化的约束就是
M1, M2 ~map[uint32]string
- 现在没有更多的类型参数可以推断,所以它只是 type-checks
bar
反对实例化的约束。 bar
的底层 (~
) 类型是否与 map[uint32]string
相同?不会,即使key和val的底层类型相同,整个map的底层类型也正好是map[someNumericID]someStringID
.
- 使用参数
foo
和 bar
实例化 equal
失败。
如果不依赖类型推断,而是使用显式类型参数实例化 equal
,这将变得更加明显。通过仅指定 M1
和 M2
(请记住它们具有相同的约束):equal[map[uint32]string, map[uint32]string](foo, bar)
那么 bar
显然不匹配。
在下面的例子中foo
和bar
基本上是同一个类型:map[uint32]string
.
然而 go1.18beta 抱怨说:M2 does not match map[K]V
.
是否有可能 equal
接受这两张地图?我是否需要更改 equal
的签名或地图本身的声明?
package main
import "fmt"
func equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func main() {
foo := map[uint32]string{
10: "bar",
}
bar := map[someNumericID]someStringID{
10: "bar",
}
if equal(foo, bar) == true {
fmt.Println("Maps are the same")
} else {
fmt.Println("Maps are not the same")
}
}
Is it even possible to get equal to accept both of these maps?
是的,但是您必须区分键和值类型,因为它们不相同。这就是固定函数的样子:
func equal[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
特别是,键和值类型参数都被限制为各自的近似元素 ~uint32
和 ~string
,以便允许转换 m2[K2(k)]
和 V2(v1)
函数体。这是比较不同类型但具有相同 底层 类型的值(包括地图索引)所必需的。
上述解决方案在地图类型上放弃了类型参数 M1
和 M2
— 由于似乎是编译器错误;有关详细信息,请参阅注释 — 但由于您实际上并未在函数体中使用这些类型,也未在 return 值中使用这些类型,因此
游乐场:https://gotipplay.golang.org/p/Y8C_8ilsXUg
如果您想了解第一个示例失败的原因,请查看此处的细分。语言规范中的相关段落是 Type inference.
- 在
equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2)
中,类型参数M1
和M2
具有相同的约束~map[K]V
。 - 当您在没有显式实例化的情况下调用函数时,编译器会尝试从提供的参数类型中推断类型参数。简而言之,它从
M1
推断出K
和V
,因此equal(foo, bar)
其中foo
是map[uint32]string
结果是K = uint32
和V = string
. - 那么实例化的约束就是
M1, M2 ~map[uint32]string
- 现在没有更多的类型参数可以推断,所以它只是 type-checks
bar
反对实例化的约束。bar
的底层 (~
) 类型是否与map[uint32]string
相同?不会,即使key和val的底层类型相同,整个map的底层类型也正好是map[someNumericID]someStringID
. - 使用参数
foo
和bar
实例化equal
失败。
如果不依赖类型推断,而是使用显式类型参数实例化 equal
,这将变得更加明显。通过仅指定 M1
和 M2
(请记住它们具有相同的约束):equal[map[uint32]string, map[uint32]string](foo, bar)
那么 bar
显然不匹配。