Go泛型新方案 - 类型参数

今天Ian Lance Taylor和Robert Griesemer新推出一篇文章,介绍了Go泛型的新方案。两位都是Go核心开发组中的老大,也是负责Go泛型特性的负责人。

本文带你了解Go泛型的最新进展,更详细的介绍请看The Next Step for GenericsType Parameters - Draft DesignSummary of Go Generics Discussionsissue#15292

在最新版的泛型设计中,原来的Contract概念被抛弃了。本来,额外增加一个类似接口的Contract概念有点画蛇添足,而且增加语言和代码的复杂性,所以最新版直接使用接口代替。

当然,这个草案离正式的Go泛型方案还有很大的变数,下一步是Go语言改变的提案,顺利的话,Go泛型的支持会在明年的8月份的1.17版本中推出。

当然, Go开发组已经提供了一个在线编译运行当前泛型方案的工具,你可以编写泛型代码尝鲜 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"fmt"
)
func Print(type T)(s []T) {
for _, v := range s {
fmt.Print(v)
}
}
func main() {
Print([]string{"Hello, ", "playground\n"})
}

如果你接触过泛型,你会很容易理解Go泛型的概念:

  • 函数可以有一个额外的泛型参数列表,使用type关键字定义,比如func F(type T)(p T) { ... }
  • 这些类型参数(type parameter)可以用在函数参数和方法体中
  • Go类型也可以有类型参数列表,比如type M(type T) []T
  • 每一个类型参数可以有一个额外的类型约束: func F(type T Constraint)(p T) { ... }
  • 类型约束是接口类型
  • 用作类型约束的接口有一个预先声明的类型列表;如果类型或者它的底层类型是这个列表中的类型之一,那么它们才算实现了这个接口 (对接口的定义进行了扩展)
  • 使用泛型函数或者泛型类型的时候,需要传入类型参数
  • 在通常情况下,类型推断允许省略类型参数(type argument)
  • 如果类型参数有类型约束,那么它的类型参数(type argument)必须要实现这个接口
  • 泛型函数只能使用类型约束所定义的那些操作

这就是当前Go泛型的主要内容。如果你已经接触过其他语言的泛型,就比较好理解Go的泛型的概念,而且会感觉它很简单,因为很多复杂的概念都省略了(下面内容比较难以理解):

  • No specialization.不像Rust可以针对特定的类型参数进行特化
  • No metaprogramming. 没有元数据的支持(反射package不会因此改变)
  • No higher level abstraction. 不调用或者初始化函数就无从谈起带类型参数的函数
  • No general type description. 只通过类型约束(接口)来约束类型参数的行为
  • 没有协变和逆变. 简化了泛型的概念。
  • 没有操作符方法.
  • 不支持currying. 使用泛型函数和类型时,必须全部指定所有的类型参数。
  • 不支持变长类型参数.
  • 不支持适配器
  • 对非类型值(如常量)不支持参数化.

类型参数的定义使用type,而且使用小括号而不是尖括号的方式:

1
2
3
4
5
6
// Print prints the elements of any slice.
// Print has a type parameter T, and has a single (non-type)
// parameter s which is a slice of that type parameter.
func Print(type T)(s []T) {
// same as above
}

我个人强烈反对这个小括号的方式,因为它很容易和函数的定义弄混,本来你在定义一个数据类型的,第一眼瞄过去很容易看成是一个函数定义。对于函数来说,两个连续的小括号也很容易让人视觉疲劳。为什么不采用Java/C++泛型语法结构呢,比如F<T>,理由比较勉强,是为了照顾Go的parser,避免go parser在解析尖括号的时候避免和操作符混淆,但是人类发明工具不就是为了简化人们的操作,让工具去实现枯燥的逻辑么?同样不采用F[T]的形式也是为了避免和数组混淆。

下面是一个使用类型约束的例子:

1
2
3
4
5
6
7
8
9
10
type Stringer interface {
String() string
}
func Stringify(type T Stringer)(s []T) (ret []string) {
for _, v := range s {
ret = append(ret, v.String())
}
return ret
}

多类型参数的例子:

1
func Print2(type T1, T2)(s1 []T1, s2 []T2) { ... }

泛型类型而不是函数,不要和函数定义混淆了:

1
type Vector(type T) []T

不支持类型的方法中使用类型参数,这一点和其它语言不相同,但是它可以使用类型定义中的类型参数。

接口的定义扩展了,支持显式地定义实现的类型:

1
2
3
type SignedInteger interface {
type int, int8, int16, int32, int64
}

SignedInteger约束指定了类型参数必须是列表中的几种类型,或者底层结果是这几种类型的类型。当使用SignedInteger约束类型参数的时候,我们可以使用这些类型共同的操作符,比如><等。

comparable是一个预定义的约束,可以使用==!=比较符。

类型推断可以简化泛型的调用,比如Print(int)([]int{1, 2, 3})就可以省略类型参数Print([]int{1, 2, 3})

然后我们欣赏一个例子做结尾。虽然Go的泛型方案做了很多简化,但是依然会给Go编程开发带来很大的变化,目前可以这依然还是一个草稿,等到提案文档发布的时候,Go泛型的特性才算基本定型,对于绝大部分的人来说,目前只需保持关注即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Package list provides a linked list of any type.
package list
// List is a linked list.
type List(type T) struct {
head, tail *element(T)
}
// An element is an entry in a linked list.
type element(type T) struct {
next *element(T)
val T
}
// Push pushes an element to the end of the list.
func (lst *List(T)) Push(v T) {
if lst.tail == nil {
lst.head = &element(T){val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element(T){val: v }
lst.tail = lst.tail.next
}
}
// Iterator ranges over a list.
type Iterator(type T) struct {
next **element(T)
}
// Range returns an Iterator starting at the head of the list.
func (lst *List(T)) Range() *Iterator(T) {
return Iterator(T){next: &lst.head}
}
// Next advances the iterator.
// It reports whether there are more elements.
func (it *Iterator(T)) Next() bool {
if *it.next == nil {
return false
}
it.next = &(*it.next).next
return true
}
// Val returns the value of the current element.
// The bool result reports whether the value is valid.
func (it *Iterator(T)) Val() (T, bool) {
if *it.next == nil {
var zero T
return zero, false
}
return (*it.next).val, true
}
// Transform runs a transform function on a list returning a new list.
func Transform(type T1, T2)(lst *List(T1), f func(T1) T2) *List(T2) {
ret := &List(T2){}
it := lst.Range()
for {
if v, ok := it.Val(); ok {
ret.Push(f(v))
}
if !it.Next() {
break
}
}
return ret
}