- 函数和类型可以有类型参数,类型参数由类型限制定义,类型限制的类型为 interface。
- 类型限制描述了 type argument 需要实现的 method 和允许传入的 type argument 类型。
- 类型限制描述了 type parameter 允许的 method 和 operation。
- 当调用支持类型参数的方法时,类型推断有时可以推断出 type argument。
类型限制
范型为了使类型限制的表达更加宽泛,把 interface 的定义从“方法的集合”扩展到了“类型的集合”,两者主要的区别是内置运算符的不同。在 Go 中, <
, ==
等内置运算符是不支持重置的,且只有内置类型可以拥有这些运算符,自定义类型是无法拥有和定义这类运算符的。原先 interface 作为“方法的集合”,无法表达出一个类型是否支持比较相等等内置运算(因为内置运算不能被定义)。为了解决这个问题, Go 引入了 underlying type 并规定凡是 underlying type 支持内置运算符的类型都能复用其 underlying type 的内置运算。interface 也不仅仅可以用来表达方法集合,而是可以用 ~underlying type
等语法表示其对应类型集合。
Go 的泛型由于类型限制的存在,看上去会和 interface 类似,在这里与 interface 的主要不同是相关方法的返回值可以是具体的类型,而不只是 interface 。同时,还有装箱(box)的区别,一个interface里包含了两个值,代表其指向元素的type和value,如果用 interface 就会发生装箱,而泛型不会有此问题。
类型推断
技术上类型推断主要由三个子技术组成:
- 类型归一化 ( Type unification )
- 函数实参类型推断
- 限制 ( constraint ) 类型推断
类型归一化
名词定义
- 一致:identical,指两个类型字面意义上的完全一样,例如 int 和 int 是 identical 的,type MyInt int 和 int 就不是了。
- 等价:equivalent,比 identical 更加宽泛,两个类型如果可以互相转化,那么就可以被判定为等价,例如上面的 type MyInt int 和 int 虽然不一致,但却是等价的。
功能描述
- 给出两个类型,如果两个类型都没有类型参数,给出这两个类型是否相等。
- 如果两个类型中有类型参数,给出这两个类型是否可能相等。
具体操作
- 带有类型参数的类型,在不考虑类型参数的前提下,该类型与比较类型在结构上必须一致 ( identical ),否则归一失败。
- 例如
[]map[T1]T2
和[]T3
在结构上是一致的,T3
可以被替换成map[T1]T2
,同理[]map[T1]bool
和[]map[string]T2
在结构上也是一致的。 - 例如
[]map[T1]T2
和int
,struct{}
,[]struct{}
等类型在结构上不可能一致。
- 例如
- 不带有类型参数的类型,与比较类型必须等价 ( equivalent ),否则归一失败。
- 两个类型一致自然等价。
- 如果两个类型是
channel
类型,忽略channel
方向后类型一致,也可以判定为等价。 - 如果两个类型的 underlying types 是一致的,那也可以判定为等价。
函数实参类型推断
名词定义
- 参数:parameter,指函数定义时指定需要传入的参数,实际上是一个占位符,没有真实值,例如
func Add(a, b int) int
中的 a 和 b 就是参数。 - 实参:argument,指函数被调用时实际传入的参数,是一个真实值,例如
sum := Add(a, 2)
中的 a 和 2 就是真实值,此示例中 a 是一个命名变量或常量,2 是一个未命名常量。
功能描述
- 在调用有类型参数的函数时,若调用方没有传入类型参数,则根据实参推断出类型参数。
具体实现
- 只有参数列表中使用的类型参数才能被推断,如果一个类型参数只作为函数的返回值或者只在函数体中被使用,那么这个类型参数无法被推断。
- 忽略被传入的类型参数(如果有的话,一部分类型推断就无需执行了),对传入的实参和参数进行归一化,归一化失败则类型推断失败。
- 对实参和参数进行归一化的过程分为两步,第一步先忽略传入的常量,因为常量的类型是不确定的,如果没有常量,或者忽略常量后也推断出了相关的类型参数,那么完成类型推断。
- 如果上述过程也没有完成类型推断,认定常量的类型为 default type,继续类型推断。
限制类型推断
功能描述
- 根据定义的类型参数限制,从一个已知类型参数推导出其他暂时未知的类型参数。
- 例如有一个函数
func Double[S ~[]E, E constraints.Integer] (s S) S
,这个函数被这样调用Double([]int{1, 2, 3})
,可以从S -> []int, S -> ~[]E
推断出,`E -> int`。 - https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#pointer-method-example 展示了对泛型方法传入非指针类型,结果无法调用类型上定义的指针方法,并利用限制类型推断解决了该问题。
具体实现
- 当一个类型参数对应的类型限制对应的 type set 只有一个类型或一个 underlying type 时,可以执行限制类型推断。
- 这一段为本人理解:在类型推断中会维护一个类型参数到类型的 map,这个 map 允许 key 即类型参数是重复的(类似 multimap),一旦一个类型参数对应到了多个类型,就会对这些类型进行归一化,得到新的类型参数到类型的映射关系,并消除重复 entry。
- 执行完所有的 constraint type inference 后,检查实际的类型是否满足类型限制规定的方法。
实际进行类型推断的执行步骤
- 利用显式传入的类型参数,构建出类型参数到类型的 mapping 关系。
- 执行第一次限制类型推断。
- 利用 typed arguments 进行函数实参类型推断。
- 再进行一次限制类型推断。
- 对剩下的 untyped arguments 进行函数实参类型推断。
- 最后进行一次限制类型推断。
可以看到限制类型推断其实就是用来整理 mapping 关系,把类型参数相同的映射关系进行归一化。
其他
一个泛型的类型可以指向其自身,但是类型参数的值和顺序不能被改变,见下:
// This type is INVALID.
type P[T1, T2 any] struct {
F *P[T2, T1] // INVALID; must be [T1, T2]
}
// ListHead is the head of a linked list.
type ListHead[T any] struct {
head *ListElement[T]
}
// ListElement is an element in a linked list with a head.
// Each element points back to the head.
type ListElement[T any] struct {
next *ListElement[T]
val T
// Using ListHead[T] here is OK.
// ListHead[T] refers to ListElement[T] refers to ListHead[T].
// Using ListHead[int] would not be OK, as ListHead[T]
// would have an indirect reference to ListHead[int].
head *ListHead[T]
}
https://research.swtch.com/generic 里面说了一个泛型窘境,编码速度、编译速度、运行速度在遇到泛型时不能被同时满足。
泛型不支持 method,主要的原因是 method 是用来和 interface 互相配合的,而 go 的 interface 不需要继承之类的绑定关系,直接转化就好了;这时候再允许 interface 中有 method 拥有自己独立的类型参数,就需要考虑何时确定类型参数的问题,对编译器的负担较大。https://go.googlesource.com/proposal/+/HEAD/design/43651-type-parameters.md#No-parameterized-methods 列举了一些方案和为什么不采用这些方案的理由。
Underlying type
- 自身就是underlying type的类型
- boolean
- numeric
- string
- type literal
- ArrayType
- StructType
- PointerType
- FunctionType
- InterfaceType
- SliceType
- MapType
- ChannelType
- 不符合以上的情况时,一个类型的underlying type是其被创建时所指向的类型的underlying type。比如:
type ( A1 = string A2 = A1 )
这里A1符合规则1,其 underlying type 是 string,A2符合规则2,其 underlying type 是 string
- 类型参数的underlying type是其类型限制的underlying type。如:
func f[P any](x P) { ... }
P类型参数的underlying type是any的underlying type: interface()
Constant default type
常量在编译时决定类型,除下面类型外,其他类型不能是常量
- numbers
- characters (runes)
- strings
- booleans
The default type of an untyped constant is bool, rune, int, float64, complex128 or string respectively