我实现了一个函数来获取映射中的键(实际上,对于不同的类型,有几个版本),我在Go 1.18中更新了该函数以使用泛型.然后我发现实验库被扩展以包含该功能,虽然我的实现几乎相同,但函数声明有一些差异,我想更好地理解.

这是我最初的通用版本(我重命名了变量以匹配标准库,以便更好地突出显示was实际上是不同的):

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
}

以下是standard-library version条:

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

我的 idea 是,这可能需要~map符号,~符号只能用于类型约束,而不能用于实际类型.这个理论唯一的问题是:我的版本在这种 map 类型上运行良好.所以我不知道它有什么用处.

推荐答案

当您需要接受and return个定义的类型时,在函数签名中使用命名类型参数是最相关的,正如您正确猜测的,并且@icza对x/exp/slices包回答了here.

你所说的"瓷砖类型"只能用于接口约束也是正确的.

现在,x/exp/maps包中的几乎所有函数实际上都不会返回命名类型M.只有maps.Clone个签名:

func Clone[M ~map[K]V, K comparable, V any](m M) M

然而,由于type unification,将签名without声明为近似约束~map[K]V仍然适用于定义的类型.根据规格:

[...], 因为定义的类型D和类型文字L从来都不是等价的,而是unification compares the underlying type of D with L

还有一个代码示例:

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
}

playground :https://go.dev/play/p/hzb2TflybZ9

当需要传递an instantiated value of the function时,额外的命名类型参数M ~map[K]V是相关的:

func main() {
    // variable of function type!
    fn := Keys[Dictionary]
    
    m := Dictionary{"foo": 1, "bar": 2}
    fmt.Println(fn(m))
}

playground :https://go.dev/play/p/hks_8bnhgsf

如果没有M ~map[K]V type参数,就不可能用定义的类型实例化这样的函数值.当然,你可以用KV分别实例化你的函数,比如

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]
}

不过,这似乎是一个相当开放的用例.

你可以在这里看到最后的playground :https://go.dev/play/p/B-_RBSqVqUD

但是请记住,x/exp/maps是一个实验性的软件包,因此签名可能会随着future 的Go版本而改变,和/或当这些函数被提升到标准库中时.

Go相关问答推荐

Go程序在并发Forking 循环中停留在syscall.Wait4

Go Net/http路由

golang 的条件储存库

如何将文件从AWS S3存储桶复制到Azure BLOB存储

在GO中创建[]字符串类型的变量

使用Go使用Gorm使用外键对数据进行排序

无法在32位计算机上运行Golang应用程序

Json.Unmarshal() 和 gin.BindJson() 之间的区别

Go-如何在递归函数中关闭通道

使用Cookie身份验证的Gorilla Golang Websocket优化

尽管存在 WaitGroup,Goroutines 似乎被打断了

golang 上基于标头的版本控制

Go 切片容量增长率

使用 golang 生成 vim

如何使用struct的方法清除除某些字段之外的struct值

go:识别重新定义标志的包

使用 bolthold 3 条件进行 boltDB 查询

在 Go 中将十六进制转换为带符号的 Int

Go 赋值涉及到自定义类型的指针

为什么从 Go 1.17 开始在 go.mod 中有两个require块?