泛型概述 , 如何定义泛型函数
1 . 泛型的定义
在仓颉编程语言中,泛型指的是参数化类型,参数化类型是一个在声明时未知并且需要在使用时指定的类型。类型声明与函数声明可以是泛型的。最为常见的例子就是 Array、Set 等容器类型。以数组类型为例,当使用数组类型 Array 时,会需要其中存放的是不同的类型,我们不可能定义所有类型的数组,通过在类型声明中声明类型形参,在应用数组时再指定其中的类型,这样就可以减少在代码上的重复。
在仓颉中,class、interface、struct 与 enum 的声明都可以声明类型形参,也就是说它们都可以是泛型的。
为了方便讨论我们先定义以下几个常用的术语:
- 类型形参:一个类型或者函数声明可能有一个或者多个需要在使用处被指定的类型,这些类型就被称为类型形参。在声明形参时,需要给定一个标识符,以便在声明体中引用。
- 类型变元:在声明类型形参后,当我们通过标识符来引用这些类型时,这些标识符被称为类型变元。
- 类型实参:当我们在使用泛型声明的类型或函数时指定了泛型参数,这些参数被称为类型实参。
- 类型构造器:一个需要零个、一个或者多个类型作为实参的类型称为类型构造器。
简而言之泛型就是声明所要使用对象的类型
2 . 如何声明泛型
类型形参在声明时一般在类型名称的声明或者函数名称的声明后,使用尖括号 <...> 括起来。例如泛型列表可声明为:
class List<T> {
var elem: Option<T> = None
var tail: Option<List<T>> = None
}
其中 List 中的 T 被称为类型形参。对于 elem: Option 中对 T 的引用称为类型变元,同理 tail: Option<List> 中的 T 也称为类型变元。函数 sumInt 的参数中 List 的 Int64 被称为 List 的类型实参。 List 就是类型构造器,List 通过 Int64 类型实参构造出了一个类型 Int64 的列表类型。
3. 全局泛型函数
在声明全局泛型函数时,只需要在函数名后使用尖括号声明类型形参,然后就可以在函数形参、返回类型及函数体中对这一类型形参进行引用。例如 id 函数定义为:
func id<T>(a: T): T {
return a
}
在这里我们声明了一个函数名为id的函数 , 同时给该函数定义了一个变量名为a , 类型为T 的变量
其中 (a: T) 是函数声明的形参,其中使用到了 id 函数声明的类型形参 T,并且在 id 函数的返回类型使用。
再比如另一个复杂的例子,定义如下一个泛型函数 composition,该函数声明了 3 个类型形参,分别是 T1, T2, T3,其功能是把两个函数 f: (T1) -> T2, g: (T2) -> T3 复合成类型为 (T1) -> T3 的函数。
func composition<T1, T2, T3>(f: (T1) -> T2, g: (T2) -> T3): (T1) -> T3 {
return {x: T1 => g(f(x))}
}
因为被用来复合的函数可以是任意类型,例如可以是 (Int32) -> Bool, (Bool) -> Int64 的复合,也可以是 (Int64) -> Rune, (Rune) -> Int8 的复合,所以才需要使用泛型函数。
func times2(a: Int64): Int64 {
return a * 2
}
func plus10(a: Int64): Int64 {
return a + 10
}
func times2plus10(a: Int64) {
return composition<Int64, Int64, Int64>(times2, plus10)(a)
}
main() {
println(times2plus10(9))
return 0
}
4. 局部泛型函数
简单来说局部泛型函数就是在一个函数中嵌套另一个泛型函数
func foo(a: Int64) {
func id<T>(a: T): T { a }
func double(a: Int64): Int64 { a + a }
return (id<Int64> ~> double)(a) == (double ~> id<Int64>)(a)
}
main() {
println(foo(1))
return 0
}