内置范型

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

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

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

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[string]int的映射类型。这两个映射类型的键值类型均为内置类型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自定义范型草案。此草案仍旧处于早期朦胧阶段,最终的设计可能会和此草案差别很大。

Go语言101项目目前同时托管在GithubGitlab上。欢迎各位在这两个项目中通过提交bug和PR的方式来改进完善Go语言101中的各篇文章。

本书微信公众号名称为"Go 101"。每个工作日此公众号将尽量发表一篇和Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。

赞赏