还不太清楚Go1.17 泛型 基本用法的同学可以看下站长之前写的 Go泛型系列:提前掌握 Go 泛型的基本使用[1] 。正好看此文也可以简单复习一下。今天土拨鼠给大家介绍下 泛型过滤(Filter)功能 的小短文(参考 Implementing a Generic Filter Function in Go[2] )。例子也很简单,希望大家能从中有所收获。
正好昨天robpike昨天提出了 反对在Go1.18标准库中加入泛型支持 的issue。大家也可以发表一下自己的看法。
功能实现:过滤一下切片中符合条件的元素,并返回
正常情况下的话土拨鼠会简单写一下,一般都是作为一次性使用。
简单版
// filter 过滤掉不符合条件的元素
func filter(items []interface{}) []interface{} {
filteredItems := []interface{}{}
for index, value := range items {
// 过滤掉奇数
if num.(int)%2 == 0 {
filteredItems = append(filteredItems, value)
}
}
return filteredItems
}
显然这样写太简单了,一点都不够优雅。条件函数的话这里可以考虑用闭包作为函数参数来稍微优化一下。这样就可以编写各种花样的过滤逻辑咯。
优化版
// filter 过滤掉不符合条件的元素
func filter(items []interface{}, fn func(index int, item interface{}) bool ) []interface{} {
filteredItems := []interface{}{}
for index, value := range items {
if fn(index, value) {
filteredItems = append(filteredItems, value)
}
}
return filteredItems
}
func main() {
var nums []interface{}
nums = append(nums, 1, 2, 3, 4, 5)
evenNums := filter(nums, func(index int, num interface{}) bool { return num.(int)%2 == 0 })
fmt.Printf("%d", evenNums[0].(int) + 2)
}
约束版
为了使得类型使用更加清晰明了一些,咱们这里使用 any 类型(可不是随便起的哦,可以参考 类型参数提案中any的约束[3] )来替代 interface{} 类型。修改如下:
type any = interface{}
// filter 过滤掉不符合条件的元素
func filter(items []any, fn func(index int, item any) bool) []any {
filteredItems := []any{}
for index, value := range items {
if fn(index, value) {
filteredItems = append(filteredItems, value)
}
}
return filteredItems
}
这里给大家讲一下什么是约束?约束主要是 确保泛型类型的元素始终满足特定的接口类型 。根据 Go 的哲学,必须在类型参数声明旁边添加约束。这个demo使用的是 any 类型等同于 interface{} ,可以接受任意类型的。所以这里的约束作用就由闭包来做了。
func filter[T SomeInterface](items []T )
大家也可以在 go2goPlayground[4] 进行测试,最终demo如下:
package main
import (
"fmt"
)
// filter 过滤掉不符合条件的元素
func filter[T any](items []T, fn func(item T "T any") bool) []T {
filteredItems := []T{}
for _, value := range items {
if fn(value) {
filteredItems = append(filteredItems, value)
}
}
return filteredItems
}
func main() {
nums := []int{1, 2, 3, 4, 5}
evenNums := filter(nums, func(num int) bool { return num%2 == 0 })
fmt.Println(evenNums)
}
关于robpike反对在Go1.18标准库中加入泛型的讨论
土拨鼠也很赞同robpike的说法,我们还需要更多的学习、使用、思考、沉淀。
下面是土拨鼠对robpike关于 go: don’t change the libraries in 1.18[5] 的翻译,关于Go1.18标准库中要不要加入泛型的支持的问题欢迎大家留言讨论。
Go语言的1.18版本很可能包括迄今为止该语言自创建以来的最大变化:参数化多态性,俗称泛型。关于核心库将如何适应,以及如何适应,已经有很多讨论。例如,可以看 #45955-slices: new package to provide generic slice functions[6] 也可以看一下煎架写的Go 提案:增加泛型版 slices 和 maps 新包和 #48594-proposal: bytes: add Grow, Clip; maybe add bytes/strings Insert, Delete[7] 的相关讨论,还有其他的已经有了,而且肯定很快会有更多。
如何在标准库中使用这些想法,需要深思熟虑和计划。现在把它们放进标准库里,也会给版本的发布带来很大的负担。
我建议不在Go1.18中更新标准库。
理由很简单,也很有说服力。 一下子要做的事情太多,而且我们可能会弄错 。语言的改变已经以某种形式进行了十多年,但库的改变是非常新的, 我们没有在Go中使用新类型的经验 ,无法为其设计提供有力的依据。是的,我们可以对它们进行详细的推理,而且已经做了很多。其他语言的经验是有帮助的,但是 Go教会了我们一件事,那就是它有自己做事的方法。
对于泛型,我们还不知道那些新的方法是什么。另外,兼容性的承诺使我们在任何细节上出错的代价都很高。我们应该等待、观察和学习。
相反,我 建议我们仍然可以设计、构建、测试和使用用于slice、 map 、channel等的新库,但首先要把它们放在 golang /x/exp 仓库中 。这样一来,这些新的库-在现阶段是真正实验性的-可以在生产中进行测试,但可以在一两个周期内进行改变、调整和发展,让整个社区尝试一下,如果他们有兴趣并且愿意接受一点不稳定的话,而不要求每个组件的每个细节都从第一天就准备好。 一旦它们经过一段时间的试验,并通过经验进行更新,我们就会把它们移到主 repo 中,就像我们对其他外部生成的 软件包 所做的那样,但我们相信它们在实践中运行良好,值得我们做出的兼容性承诺 。
我意识到每个人都想体验新语言功能带来的乐趣,并期待着修复核心库中的一些问题,一旦它到来,就不会那么笨拙,但我坚信现在最好慢慢来。使用、学习、研究,并谨慎地做出改动。
参考资料
[1]
Go泛型系列:提前掌握Go泛型的基本使用:
[2]
Implementing a Generic Filter Function in Go:
[3]
类型参数提案中any的约束: #the-constraint
[4]
go2goPlayground:
[5]
go: don’t change the libraries in 1.18:
[6]
#45955-slices: new package to provide generic slice functions:
[7]
#48594-proposal: bytes: add Grow, Clip; maybe add bytes/strings Insert, Delete:
