今天Ian Lance Taylor和Robert Griesemer新推出一篇文章,介绍了Go泛型的新方案。两位都是Go核心开发组中的老大,也是负责Go泛型特性的负责人。
本文带你了解Go泛型的最新进展,更详细的介绍请看The Next Step for Generics、Type Parameters - Draft Design、Summary of Go Generics Discussions、issue#15292。
在最新版的泛型设计中,原来的Contract
概念被抛弃了。本来,额外增加一个类似接口的Contract
概念有点画蛇添足,而且增加语言和代码的复杂性,所以最新版直接使用接口代替。
当然,这个草案离正式的Go泛型方案还有很大的变数,下一步是Go语言改变的提案,顺利的话,Go泛型的支持会在明年的8月份的1.17版本中推出。
当然, Go开发组已经提供了一个在线编译运行当前泛型方案的工具,你可以编写泛型代码尝鲜 :
|
|
如果你接触过泛型,你会很容易理解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
,而且使用小括号而不是尖括号的方式:
|
|
我个人强烈反对这个小括号的方式,因为它很容易和函数的定义弄混,本来你在定义一个数据类型的,第一眼瞄过去很容易看成是一个函数定义。对于函数来说,两个连续的小括号也很容易让人视觉疲劳。为什么不采用Java/C++泛型语法结构呢,比如F<T>
,理由比较勉强,是为了照顾Go的parser,避免go parser在解析尖括号的时候避免和操作符混淆,但是人类发明工具不就是为了简化人们的操作,让工具去实现枯燥的逻辑么?同样不采用F[T]
的形式也是为了避免和数组混淆。
下面是一个使用类型约束的例子:
|
|
多类型参数的例子:
|
|
泛型类型而不是函数,不要和函数定义混淆了:
|
|
不支持类型的方法中使用类型参数,这一点和其它语言不相同,但是它可以使用类型定义中的类型参数。
接口的定义扩展了,支持显式地定义实现的类型:
|
|
SignedInteger
约束指定了类型参数必须是列表中的几种类型,或者底层结果是这几种类型的类型。当使用SignedInteger
约束类型参数的时候,我们可以使用这些类型共同的操作符,比如>
、<
等。
comparable
是一个预定义的约束,可以使用==
和!=
比较符。
类型推断可以简化泛型的调用,比如Print(int)([]int{1, 2, 3})
就可以省略类型参数Print([]int{1, 2, 3})
。
然后我们欣赏一个例子做结尾。虽然Go的泛型方案做了很多简化,但是依然会给Go编程开发带来很大的变化,目前可以这依然还是一个草稿,等到提案文档发布的时候,Go泛型的特性才算基本定型,对于绝大部分的人来说,目前只需保持关注即可。
|
|