为什么Go中maps.Keys()指定map类型为M?
Why does maps.Keys() in Go specify the map type as M?
我实现了一个函数来获取映射中的键(实际上有几个版本,用于不同的类型),我更新它以在 Go 1.18 中使用泛型。然后我发现实验库已扩展以包含该功能,虽然我的实现几乎相同,但函数声明有一些我想更好地理解的差异。
这是我原来的通用版本(我重命名了变量以匹配标准库,以更好地突出显示实际上是不同的):
func mapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
如您所见,主要区别在于额外的 M ~map[K]V
类型参数,我将其省略并直接使用 map[K]V
作为函数的参数类型。我的函数有效,那么为什么我需要经历添加第三个参数化类型的额外麻烦?
当我写我的问题时,我想我已经找到了答案:能够在真正映射的类型上调用函数,但没有直接声明为映射,就像可能在这个DataCache
类型:
type DataCache map[string]DataObject
我的想法是,这可能需要 ~map
符号,而 ~
只能用于类型约束,不能用于实际类型。该理论的唯一问题是:我的版本在此类地图类型上运行良好。所以我不知道它有什么用。
tl;dr 它与 非常不常见的 情况相关,您需要声明一个函数类型的变量(而不调用它),然后使用另一个包中的命名映射类型实例化该函数,该包在其定义中使用未导出的类型。
当您需要接受 和 return 定义的类型时,在函数签名中使用命名类型参数最为相关,正如您正确猜测的那样,正如@icza 回答的那样 关于 x/exp/slices
包。
您关于“代字号类型”只能用于接口约束的说法也是正确的。
现在,x/exp/maps
包中的几乎所有函数实际上 return 命名类型 M
。唯一真正做到的是 maps.Clone
签名:
func Clone[M ~map[K]V, K comparable, V any](m M) M
然而,声明签名 没有 近似约束 ~map[K]V
仍然适用于定义的类型,这要归功于 type unification。来自规格:
[...], because a defined type D
and a type literal L
are never equivalent, unification compares the underlying type of D with L instead
还有一个代码示例:
func Keys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
type Dictionary map[string]int
func main() {
m := Dictionary{"foo": 1, "bar": 2}
k := Keys(m)
fmt.Println(k) // it just works
}
游乐场:https://go.dev/play/p/hzb2TflybZ9
与附加命名类型参数 M ~map[K]V
相关的情况是当您需要传递 函数的实例化值时 :
func main() {
// variable of function type!
fn := Keys[Dictionary]
m := Dictionary{"foo": 1, "bar": 2}
fmt.Println(fn(m))
}
游乐场:https://go.dev/play/p/hks_8bnhgsf
如果没有 M ~map[K]V
类型参数,就不可能用定义的类型实例化这样一个函数值。当然你可以用 K
和 V
分别实例化你的函数,比如
fn := Keys[string, int]
但是当定义的地图类型属于不同的包并引用未导出的类型时,这是不可行的:
package foo
type someStruct struct{ val int }
type Dictionary map[string]someStruct
和:
package main
func main() {
// does not compile
// fn := Keys[string, foo.someStruct]
// this does
fn := maps.Keys[foo.Dictionary]
}
不过,这似乎是一个相当通俗易懂的用例。
你可以在这里看到最后的游乐场:https://go.dev/play/p/B-_RBSqVqUD
但是请记住,x/exp/maps
是一个实验包,因此签名可能会随着未来的 Go 版本而改变,and/or 当这些函数被提升到标准库中时。
我实现了一个函数来获取映射中的键(实际上有几个版本,用于不同的类型),我更新它以在 Go 1.18 中使用泛型。然后我发现实验库已扩展以包含该功能,虽然我的实现几乎相同,但函数声明有一些我想更好地理解的差异。
这是我原来的通用版本(我重命名了变量以匹配标准库,以更好地突出显示实际上是不同的):
func mapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
如您所见,主要区别在于额外的 M ~map[K]V
类型参数,我将其省略并直接使用 map[K]V
作为函数的参数类型。我的函数有效,那么为什么我需要经历添加第三个参数化类型的额外麻烦?
当我写我的问题时,我想我已经找到了答案:能够在真正映射的类型上调用函数,但没有直接声明为映射,就像可能在这个DataCache
类型:
type DataCache map[string]DataObject
我的想法是,这可能需要 ~map
符号,而 ~
只能用于类型约束,不能用于实际类型。该理论的唯一问题是:我的版本在此类地图类型上运行良好。所以我不知道它有什么用。
tl;dr 它与 非常不常见的 情况相关,您需要声明一个函数类型的变量(而不调用它),然后使用另一个包中的命名映射类型实例化该函数,该包在其定义中使用未导出的类型。
当您需要接受 和 return 定义的类型时,在函数签名中使用命名类型参数最为相关,正如您正确猜测的那样,正如@icza 回答的那样x/exp/slices
包。
您关于“代字号类型”只能用于接口约束的说法也是正确的。
现在,x/exp/maps
包中的几乎所有函数实际上 return 命名类型 M
。唯一真正做到的是 maps.Clone
签名:
func Clone[M ~map[K]V, K comparable, V any](m M) M
然而,声明签名 没有 近似约束 ~map[K]V
仍然适用于定义的类型,这要归功于 type unification。来自规格:
[...], because a defined type
D
and a type literalL
are never equivalent, unification compares the underlying type of D with L instead
还有一个代码示例:
func Keys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
type Dictionary map[string]int
func main() {
m := Dictionary{"foo": 1, "bar": 2}
k := Keys(m)
fmt.Println(k) // it just works
}
游乐场:https://go.dev/play/p/hzb2TflybZ9
与附加命名类型参数 M ~map[K]V
相关的情况是当您需要传递 函数的实例化值时 :
func main() {
// variable of function type!
fn := Keys[Dictionary]
m := Dictionary{"foo": 1, "bar": 2}
fmt.Println(fn(m))
}
游乐场:https://go.dev/play/p/hks_8bnhgsf
如果没有 M ~map[K]V
类型参数,就不可能用定义的类型实例化这样一个函数值。当然你可以用 K
和 V
分别实例化你的函数,比如
fn := Keys[string, int]
但是当定义的地图类型属于不同的包并引用未导出的类型时,这是不可行的:
package foo
type someStruct struct{ val int }
type Dictionary map[string]someStruct
和:
package main
func main() {
// does not compile
// fn := Keys[string, foo.someStruct]
// this does
fn := maps.Keys[foo.Dictionary]
}
不过,这似乎是一个相当通俗易懂的用例。
你可以在这里看到最后的游乐场:https://go.dev/play/p/B-_RBSqVqUD
但是请记住,x/exp/maps
是一个实验包,因此签名可能会随着未来的 Go 版本而改变,and/or 当这些函数被提升到标准库中时。