泛型

Go语言目前(Go 1.17)不支持自定义泛型,只支持一等公民组合类型的内置泛型。 我们可以用各种一等公民组合类型来组合出无穷个类型。

本文将展示一些自定义组合类型的例子并解释如何解读这些自定义类型。

一些自定义组合类型的例子

Go中的组合类型字面表示设计得非常直观和易于解读。 即使对于一些非常复杂的类型,我们也几乎不可能在解读它们的字面形式中迷失。 下面将从简单到复杂列出一些自定义组合类型的例子并进行解读。

先看一个简单的例子:

  1. [3][4]int

当解读一个类型的字面形式时,我们应该从左到右进行解读。 左边开头的[3]表示着这个类型为一个数组类型,它右边的整个部分为它的元素类型。 对于这个例子,它的元素类型为另外一个数组类型[4]int。 此另外一个数组类型的元素类型为为内置类型int。 第一个数组类型可以被看作是一个二维数组类型。

一个使用此数组类型的例子:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. matrix := [3][4]int{
  7. {1, 0, 0, 1},
  8. {0, 1, 0, 1},
  9. {0, 0, 1, 1},
  10. }
  11. matrix[1][1] = 3
  12. a := matrix[1] // 变量a的类型为[4]int
  13. fmt.Println(a) // [0 3 0 1]
  14. }

类似的,

  • [][]string是一个元素类型为另一个切片类型[]string的切片类型。
  • **bool是一个基类型为另一个指针类型*bool的指针类型。
  • chan chan int是一个元素类型为另一个通道类型的chan int的通道类型。
  • map[int]map[int]string是一个元素类型为另一个映射类型map[int]string的映射类型。 这两个映射类型的键值类型均为内置类型int
  • func(int32) func(int32)是一个只有一个输入参数和一个返回值的函数类型,此返回值的类型为一个只有一个输入参数的函数类型。 这两个函数类型的输入参数的类型均为内置类型int32

下面是另一个自定义组合类型:

  1. chan *[16]byte

最左边的chan关键字表明此类型是一个通道类型。 chan关键字右边的整个部分*[16]byte表示此通道类型的元素类型,此元素类型是一个指针类型。 此指针类型的基类型为*右边的整个部分:[16]byte,此基类型为一个数组类型。 此数组类型的元素类型为内置类型byte

一个使用此通道类型的例子:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. "crypto/rand"
  6. )
  7. func main() {
  8. c := make(chan *[16]byte)
  9. go func() {
  10. // 使用两个数组以避免数据竞争。
  11. var dataA, dataB = new([16]byte), new([16]byte)
  12. for {
  13. _, err := rand.Read(dataA[:])
  14. if err != nil {
  15. close(c)
  16. } else {
  17. c <- dataA
  18. dataA, dataB = dataB, dataA
  19. }
  20. }
  21. }()
  22. for data := range c {
  23. fmt.Println((*data)[:])
  24. time.Sleep(time.Second / 2)
  25. }
  26. }

类似的,类型map[string][]func(int) int为一个映射类型。 此映射类型的键值类型为内置类型string,右边剩余的部分为此映射类型的元素类型。 []表明此映射的元素类型为一个切片类型,此切片类型的元素类型为一个函数类型func(int) int

一个使用了此映射类型的例子:

  1. package main
  2. import "fmt"
  3. func main() {
  4. addone := func(x int) int {return x + 1}
  5. square := func(x int) int {return x * x}
  6. double := func(x int) int {return x + x}
  7. transforms := map[string][]func(int) int {
  8. "inc,inc,inc": {addone, addone, addone},
  9. "sqr,inc,dbl": {square, addone, double},
  10. "dbl,sqr,sqr": {double, double, square},
  11. }
  12. for _, n := range []int{2, 3, 5, 7} {
  13. fmt.Println(">>>", n)
  14. for name, transfers := range transforms {
  15. result := n
  16. for _, xfer := range transfers {
  17. result = xfer(result)
  18. }
  19. fmt.Printf(" %v: %v \n", name, result)
  20. }
  21. }
  22. }

下面是一个看上去有些复杂的类型:

  1. []map[struct {
  2. a int
  3. b struct {
  4. x string
  5. y bool
  6. }
  7. }]interface {
  8. Build([]byte, struct {x string; y bool}) error
  9. Update(dt float64)
  10. Destroy()
  11. }

让我们从左到右解读此类型。 最左边开始的[]表明这是一个切片类型,紧跟着的map关键字表明此切片类型的元素为一个映射类型。 map关键字后紧跟的一对方括号[]中的结构体类型字面形式表明此映射的键值类型为一个结构体类型。 此中括号右边的整个部分表明此映射的元素类型为一个接口类型。此接口类型指定了三个方法。 此映射的键值结构体类型有两个字段,第一个字段的名称和类型为a和内置类型int; 第二个字段的名称为b,它的类型为另外一个结构体类型struct {x string; y bool}。 此另外一个结构体类型也有两个字段:内置string类型的字段x和内置bool类型的字段y

请注意第二个结构体类型也被用做刚提及的接口类型所指定的其中一个方法中的其中一个参数类型。

我们经常将复杂类型的各个组成部分单独提前声明为一个类型名,从而获得更高的可读性。 下面的代码中的类型别名T和上面刚解读的类型表示同一个类型。

  1. type B = struct {
  2. x string
  3. y bool
  4. }
  5. type K = struct {
  6. a int
  7. b B
  8. }
  9. type E = interface {
  10. Build([]byte, B) error
  11. Update(dt float64)
  12. Destroy()
  13. }
  14. type T = []map[K]E

当前Go中的泛型支持现状

Go中当前的内置泛型除了上述类型组合,还有一些支持泛型的内置函数。 比如,内置函数len可以用来获取各种容器值的长度。 unsafe标准库包中的函数也可以被看作是支持泛型的内置函数。

Go目前不支持自定义泛型这一事实有时候确实造成了一些不方便。 比如,math标准库包中的函数的参数类型和结果类型都为内置类型float64。 当调用这些函数的时候,我们必须将非float64类型的实参值转换为float64,然后还需把结果从float64类型转回来。 这不仅不方便,而且代码执行效率不高。

幸运的是,很多Go项目对自定义泛型并没有很强的需求;或者缺少自定义泛型对很多Go项目来说并不是一个不可克服的障碍。 并且,缺少自定义泛型的不足可以通过(在运行时刻)使用反射机制和(在编译时刻)代码生成来部分地弥补。

未来的Go自定义泛型

Go语言核心设计和开发团队并不排斥在Go中加入自定义泛型, 未来的Go版本中还是很可能会支持自定义泛型的。 事实上,自定义泛型设计已经朝着更成熟的方向演化了好几个版本了。 我们现在甚至已经可以在这里在线试验开发版特性中的自定义泛型特性。 我们也可以自行编译Go项目dev.go2go分支在本地试验这些特性。

自定义泛型可期,让我们拭目以待。