对于是否会在以“less is more”为原则的golang语言中增加泛型(generic)特性一直颇有争议,直到官方确定泛型是go2发展的重点才一锤定音。go 1.18中即将正式发布泛型特性,当前go 1.18beta1已经发布,让我们对泛型尝尝鲜吧。
安装go 1.18 beta1
当前可有如下两种方式来安装go 1.18beta1。
使用go install安装
此安装方式要求环境中已经安装go,然后可以通过如下命令进行安装。
go install golang.org/dl/go1.18beta1@latest
注意:此种方式会将安装到$GOPATH/bin/目录下,且安装后的二进制名称为go1.18beta1而非go。
使用binary release版本进行安装
此安装方式对环境没有要求,具体操作如下:
$ go version
go version go1.18beta1 linux/amd64
从简单性上来说推荐使用此种方式进行安装,我本人也是采用此方式。
尝鲜Demo
在编程实践中我们经常遇到从一个数组(或切片)中筛选出满足条件元素的需求,之前大家一般会针对每一种类型的数组写一个筛选函数(filter),这些函数间除了名称及类型不同外其它几乎完全一样。对这些重复敏感的人,会选择使用反射进行重构,但基于反射的实现很难进行静态校验。泛型的到来,为优雅地解决此问题提供了便利条件。
创建go project
$ cd $GOPATH/src
$ mkdir hellogeneric
$ go mod init
go: creating new go.mod: module hellogeneric
非泛型时写法
过滤int及float64的函数如下:
func FilterInts(elems []int, predicate func(int) bool) []int {
var r []int
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
func FilterFloat64s(elems []float64, predicate func(float64) bool) []float64 {
var r []float64
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
相应的main函数及运行结果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("No-Generic filters: %v and %v\n",
FilterInts(ints, predicateOfInt),
FilterFloat64s(float64s, predicateOfFloat64))
}
$ go run .
No-Generic filters: [2 4 6] and [5.5 6.6]
使用反射的写法
filter函数实现如下:
func FilterByReflect(elems, predicate interface{}) interface{} {
elemsValue := reflect.ValueOf(elems)
if elemsValue.Kind() != reflect.Slice {
panic("filter: wrong type, not a slice")
}
predicateValue := reflect.ValueOf(predicate)
if predicateValue.Kind() != reflect.Func {
panic("filter: wrong type, not a func")
}
if (predicateValue.Type().NumIn() != 1) ||
(predicateValue.Type().NumOut() != 1) ||
(predicateValue.Type().In(0) != elemsValue.Type().Elem()) ||
(predicateValue.Type().Out(0) != reflect.TypeOf(true)) {
panic("filter: wrong type, predicate must be of type func(" +
elemsValue.Elem().String() + ") bool")
}
var indexes []int
for i := 0; i < elemsValue.Len(); i++ {
if predicateValue.Call([]reflect.Value{elemsValue.Index(i)})[0].Bool() {
indexes = append(indexes, i)
}
}
r := reflect.MakeSlice(elemsValue.Type(), len(indexes), len(indexes))
for i := range indexes {
r.Index(i).Set(elemsValue.Index(indexes[i]))
}
return r.Interface()
}
相应的main函数及运行结果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("Reflect filters: %v and %v\n",
FilterByReflect(ints, predicateOfInt),
FilterByReflect(float64s, predicateOfFloat64))
}
$ go run .
Reflect filters: [2 4 6] and [5.5 6.6]
使用泛型的写法
filter函数的实现如下:
func FilterByGeneric[V int | float64](elems []V, predicate func(V)bool) []V {
var r []V
for _, e := range elems {
if predicate(e) {
r = append(r, e)
}
}
return r
}
泛型函数相对传统函数参数而言,增加了类型参数(type parameters),这些类型参数使得函数具备了泛型能力。在调用时,也需要指定类型参数和函数参数,由于go语言具有类型推断能力通常可以省略类型参数的填写。
int | float64
type FilterElem interface {
int | float64
}
也即可将类型限定定义为接口(interface),然后再修改相应泛型函数定义即可:
func FilterByGeneric[V FilterElem](elems []V, predicate func(V)bool) []V
对于filter而言,除了int和float64之外,对于其它作意类型应该也是适用的,此时我们可以使用any类型限定来表达此能力:
func FilterByGeneric[V any](elems []V, predicate func(V)bool) []V
相应的main函数及运行结果如下:
func main() {
ints := []int{1, 2, 3, 4, 5, 6}
predicateOfInt := func(i int) bool { return i%2 == 0 }
float64s := []float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6}
predicateOfFloat64 := func(f float64) bool { return f >= 5.0 }
fmt.Printf("Generic filters: %v and %v\n",
FilterByGeneric(ints, predicateOfInt),
FilterByGeneric(float64s, predicateOfFloat64))
}
$ go run .
Generic filters: [2 4 6] and [5.5 6.6]
后记
到这里我们就基本上对go的泛型特性进行了尝鲜体验,其在一定程度上确实会使我们写出来的代码更加简洁,但是否能真正在正式项目中使用我们还需要对此特性进行深入学习、并对其进行商用评估。
references
Go Programming Patterns: Gopher China 2020 陈皓(左耳朵)