Golang泛型初体验

对于是否会在以“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语言具有类型推断能力通常可以省略类型参数的填写。

对于每一个类型参数,都有一个类型限定(type constraint)用于描述类型参数的元数据,以指定调用时可允许的类型。类型限定通常代表一组类型,如本例中的int | float64就表示允许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的泛型特性进行了尝鲜体验,其在一定程度上确实会使我们写出来的代码更加简洁,但是否能真正在正式项目中使用我们还需要对此特性进行深入学习、并对其进行商用评估。

完整代码见:https://github.com/skholee/hellogeneric

references

Go Programming Patterns: Gopher China 2020 陈皓(左耳朵)

https://golang.google.cn/doc/tutorial/generics

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 在 Go1.17 中,千呼万唤的泛型终于出来了,但又没有完全出来。在 Go1.17 的发布文档中,并没有提到泛型,...
    rayjun阅读 10,524评论 0 5
  • gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf作为序列化工具。其客户端提供...
    CZ_Golang阅读 82,308评论 9 71
  • 在安装之间,我们先了解Golang Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应...
    技术流刘阅读 4,245评论 0 0
  • 最近在学习golang,看网上很多人都喜欢爬豆瓣,今天我就写了一个golang版的爬虫。对于python爬虫,我很...
    若与阅读 13,724评论 1 18
  • GoPlus为国内云厂商七牛云推出的一门静态类型语言,与 Go 语言完全兼容。其代码样式类似脚本,并且比 Go 更...
    写个代码容易么阅读 11,927评论 3 6

友情链接更多精彩内容