泛型
-
泛型指的是参数化类型,参数化类型是一个在声明时未知并且需要在使用时指定的类型 -
函数、class、interface、struct、enum可以声明类型形参,做成泛型结构的- 类型形参:一个类型或者函数声明 需要在使用处被指定的类型。给定一个标识符以便在声明体中引用
- 类型变元:引用类型形参的标识符
- 类型实参:使用泛型声明的类型或函数时指定的泛型参数
- 类型构造器:一个需要零个、一个或者多个类型作为实参的类型
// T 是 类型形参
// List 是类型构造器
class List<T> {
// elm和tail中的T是类型变元
let elem: Option<T> = None
let tail: Option<List<T>> = None
}
// Int64 是类型实参
func sumInt(a: List<Int64>) { println(a.elem ?? 0) }
-
如果一个函数声明了一个或多个类型形参,则将其称为泛型函数
- 根据函数范围,可以分为全局泛型函数、局部泛型函数、成员泛型函数
- class、struct 与 enum 的成员函数可以是泛型的。
class 中声明的泛型成员函数不能被 open 修饰 - 扩展中的函数也可以是泛型的
- interface、class、struct、enum 与 extend 中可以定义静态泛型函数
-
泛型类型也存在父子关系,具体可以查看
- A 和 B 是(实例化后的)类型,T 是类型构造器。设有一个类型参数 X(例如 interface T<X>)
- 当且仅当 A = B,T(A) <: T(B)成立,则 T 是不型变的。
- 当且仅当 A <: B,T(A) <: T(B)成立,则 T 在 X 处是协变的。
- 当且仅当 B <: A,T(A) <: T(B)成立,则 T 在 X 处是逆变的
- 所有 用户自定义的泛型类型 在其所有的类型变元处 都是不型变 的
内建的元组类型对其每个元素类型来说,都是协变的;内建的函数类型在其入参类型处是逆变的,在其返回类型处是协变的
-
类型别名可以当做类型使用,也可以当做构造器使用
-
只能在源文件顶层定义类型别名,并且
原类型必须在别名定义处可见 -
一个(或多个)类型别名定义中
禁止出现(直接或间接的)循环引用 -
类型别名并不会定义一个新的类型,它仅仅是为原类型定义了另外一个名字
- 作为类型使用
- 当类型别名实际指向的类型为 class、struct 时,可以作为构造器名称使用
- 当类型别名实际指向的类型为 class、interface、struct 时,可以作为访问内部静态成员变量或函数的类型名
- 当类型别名实际指向的类型为 enum 时,可以作为 enum 声明的构造器的类型名
-
-
目前的泛型约束只能使用where子句。像T<:ToString的语法目前还没有
- 约束大致分为接口约束与子类型约束
- 同一个类型变元的多个约束可以使用 & 连接
import std.collection.ArrayList
// 泛型接口
interface GenericInterface<T> where T <: ToString {
// 规定一个函数,可以打印数据类型
func printT(e: T): Unit
}
// 全局泛型函数
func globalGenericFunc<T>(a: T): T {
// 局部泛型函数
func localGenericFunc<U>(b: U): U {
b
}
// 调用局部泛型函数 处理 全局泛型 函数 传入的实参a
localGenericFunc(a)
}
// 扩展中的函数也可以是泛型的
extend Int64 {
// 对T使用泛型约束
func printIntAndArg<T>(a: T) where T <: ToString {
println(this)
println("${a}")
}
}
// T 是 类型形参
// List 是类型构造器
// 泛型类
class GenericClass<T> {
// elm和tail中的T是类型变元
let elem: Option<T> = None
let tail: Option<GenericClass<T>> = None
// 泛型成员函数。
// 使用 where 限制了T的类型,这样可以调用println打印出a
func memberGenericFunc<T>(a: T): Unit where T <: ToString {
println(a)
}
// 静态泛型函数
public static func staticGenericFunc<T>(arr: ArrayList<T>, default: T): (T, T) {
if (arr.size < 2) {
return (default, default)
}
return (arr[0], arr[1])
}
}
// Int64 是类型实参
func sumInt(a: GenericClass<Int64>) {
println(a.elem ?? 0)
}
// 泛型结构体 实现 泛型接口
struct GenericStruct<T, U> <: GenericInterface<T> where T <: ToString {
GenericStruct(private let x: T, private let y: U) {
println("泛型结构体 主构造函数被调用")
}
public func first(): T {
x
}
public func second(): U {
y
}
public func printT(e: T): Unit {
println(e)
}
}
// 泛型枚举
enum GenericEnum<T> {
// One可以携带一个元组类型的数据
| One(T, T)
// Two 不带数据
| Two
}
// 类型别名
type EnumInt = GenericEnum<Int>
public func genericDemo() {
// 调用全局泛型函数
globalGenericFunc<Int>(1)
// 泛型类
let genericClass = GenericClass<String>()
// 调用泛型成员函数
genericClass.memberGenericFunc<String>("泛型成员函数")
// 调用泛型静态成员函数
// 泛型类和泛型静态成员函数的类型实参可以不一样
// 同时这里ArrayList采用了类型推断
GenericClass<String>.staticGenericFunc<Int>(ArrayList([1, 2, 3]), 3)
// 泛型结构体
let genericStruct = GenericStruct("", 33)
// 类型别名 调用构造器
let e = EnumInt.One(4, 5)
}
扩展
-
扩展可以为在当前 package 可见的类型(除函数、元组、接口)添加新功能
- 可以
添加成员函数。包括实例成员函数和静态成员函数 - 可以
添加操作符重载函数。只能给实例添加 - 可以
添加成员属性。包括实例成员属性和静态成员属性 - 可以
实现接口 - 不能增加成员变量。因为原类型结构已固定
- 扩展的函数和属性必须拥有实现 并且 不能使用 open、override、 redef修饰
- 不能访问原类型 private 的成员
- 可以
-
根据扩展有没有实现新的接口,扩展可以分为 直接扩展 和 接口扩展
-
直接扩展:给类型直接添加函数、属性等
- 直接扩展的函数、属性等只能在本包中使用
- 可以给泛型扩展,包括针对特定类型形参的扩展和任意类型形参的扩展。
- 这些类型形参必须直接或间接的全部出现在被扩展的泛型的类型形参上
- 可以给扩展的泛型形参添加where子句进行类型约束
- 只有类型实参完全匹配扩展的特定类型或类型约束的条件,才能调用扩展添加的方法
-
接口扩展:让类型继承接口,实现接口要求的属性、函数等
- 可以同时扩展多个接口,多个接口要求的函数和属性要全部实现
- 可以给泛型扩展接口,也可以添加泛型形参的类型约束
- 如果被扩展的类型本身已经实现了接口所要求的函数、属性。只需要声明继承接口就行,不需要实现接口(不允许扩展实现类型原有的函数或其它扩展已经实现的函数)
-
-
给类型增加的扩展,如果想要导出给其他人使用,有一些条件
扩展本身不能使用修饰符修饰- 扩展成员可使用的修饰符有:private、internal、
protected(仅适用于被扩展类型是class)、public、static、mut(仅适用于被扩展类型是struct)。- private: 只能在本扩展内访问,外部不可见
- internal: 只有本包内可以访问
- protected: 仅适用于class,本包内可以访问,包外的当前 class 子类也可以访问
- public: 包内包外都可以访问
- static: 只能通过类型名访问,不能通过实例对象访问
- mut: 仅适用于struct,修饰用于修改struct 成员变量的函数
- 扩展内的成员定义不支持使用 open、override、redef 修饰
仓颉不允许定义孤儿扩展,接口扩展要么和 接口(包含接口继承链上的所有接口)的定义在 同一个包中,要么和 被扩展类型的定义 在同一个包中,要么都在同一个包中扩展中可以使用this访问被扩展类型的非private修饰的成员扩展中不能遮盖被扩展类型的成员,也不能遮盖其他扩展添加的成员- 在同一个 package 内对同一类型可以扩展多次
- 在扩展中可以直接调用(不加任何前缀修饰)其它对同一类型的扩展中的非 private 修饰的函数
-
泛型类型的任意两个扩展之间的可见性规则
- 如果两个扩展的约束相同,则两个扩展相互可见,即两个扩展内可以直接使用对方内的函数或属性;
- 如果两个扩展的约束不同,且两个扩展的约束有包含关系,约束更宽松的扩展对约束更严格的扩展可见,反之,不可见;
- 当两个扩展的约束不同时,且两个约束不存在包含关系,则两个扩展均互相不可见
-
扩展的导出有一套特殊的规则-
对于直接扩展,只有
当扩展与被扩展的类型在同一个 package,并且被扩展的类型和扩展中添加的成员都使用 public 或 protected 修饰时,扩展的功能才会被导出。其他情况不能导出- 通俗的说就是,扩展和被扩展的类型要在同一个文件夹下,同时扩展添加的成员和被扩展的类型必须能被其他包访问(这就需要使用public、protected访问)
-
对于接口扩展,接口扩展和接口或被扩展的类型必须定义同一个package中,接口或被扩展的类型使用public修饰时,扩展的功能会被导出
- 接口扩展和被扩展类型在同一个 package
- 接口扩展和接口在同一个 package
- 接口扩展和接口以及被扩展类型 同时在一个package
-
扩展的导入不需要显式地用 import 导入,只需要导入 被扩展的类型和接口,就可以导入可访问的所有扩展
-
定义用于扩展的接口
package cj_exercise.interfaces
// 定义接口,用于扩展
public interface I4Extend {
func g():Unit
}
// 如果接口扩展与接口在同一个 package,则只有当接口是使用 public 修饰时,扩展的功能才会被导出
extend<T> Array<T> {
func g() {
println('Array<T> 实现 I4Extend 的 g函数')
}
}
定义被扩展的类型
package cj_exercise.extends
import cj_exercise.interfaces.I4Extend
// MyExtendsClass 是被扩展的类型,并且被public修饰
public class MyExtendsClass {}
// 直接扩展的导出
// 扩展(extend MyExtendsClass)与被扩展的类型(MyExtendsClass)在同一个 package
// 被扩展的类型(MyExtendsClass)和扩展中添加的成员(publicFunc(),protectedFunc())都使用 public 或 protected 修饰时,扩展的功能才会被导出
extend MyExtendsClass {
// 只能在本扩展内使用
private func privateFunc() {}
// 可以在本包内使用
internal func internalFunc() {}
// 可以在本包以及其他包中本类的子类使用。只能在class中使用
protected func protectedFunc() {}
// 可以在其他包中使用
public func publicFunc() {}
// 只能通过类名访问。它前面可以使用private、internal、protected、public修饰
// 因为staticFunc前面使用了public修饰,所以它也被导出了
public static func staticFunc() {}
}
// 接口扩展的导出
// 如果接口扩展和被扩展类型在同一个 package
// 但接口是来自导入的,只有当被扩展类型使用 public 修饰时,扩展的功能才会被导出
extend MyExtendsClass <: I4Extend {
public func g() {
println('MyExtendsClass 实现 I4Extend 的 g函数')
}
}
使用扩展的地方
package cj_exercise.study
import std.sort.SortExtension
// 对于接口扩展,需要同时导入被扩展的类型和扩展的接口
import cj_exercise.extends.MyExtendsClass
import cj_exercise.interfaces.I4Extend
// 直接扩展
extend String {
public func printSize() {
// 可以通过this访问原有类型。这里是String
println("the size is ${this.size}")
}
}
// 使用直接扩展 扩展泛型
class MyList<T> {
public let data: Array<T> = Array<T>()
}
// 针对特定泛型实例化类型进行扩展
// 增加的功能只有在类型完全匹配时才能使用
// 且 泛型类型的类型实参 必须 符合泛型类型定义处的约束 要求
extend MyList<Int> {
func sum(): Int {
this.data.iterator().reduce({i:Int,o:Int=>i + o}).getOrDefault({=>0})
}
}
// 使用泛型形参进行扩展
// 用来扩展未实例化或未完全实例化的泛型类型
// 在 extend 后声明的全部泛型形参必须被直接或间接使用在被扩展的泛型类型上。
// 为这些类型增加的功能只有在类型和约束完全匹配时才能使用
// extend后面的泛型形参T直接使用在MyList上
extend<T> MyList<T> {
// 获取原始类型
func orgData(): Array<T> {
data
}
}
// 在扩展的同时给T添加类型约束,必须符合Equatable接口
// 这里只有可以比较的T类型才能使用firstGTSecond方法
// 像我们上一个扩展,没有添加任何约束
extend<T> MyList<T> where T <: Equatable<T> {
func firstEQSecond(): Bool {
if (data.size < 2) {
false
}
data[0] == data[1]
}
}
// extend后面的泛型形参T和R,组成元组类型间接使用在MyList上
extend<T,R> MyList<(T,R)> {}
// 接口扩展
// 声明接口
protected interface Sizeable {
prop size: Int
}
// 声明Eq接口
interface Eq<T> {
func equals(other: T): Bool
}
// 声明一个泛型类
class ExtendPair<T1, T2> {
var first: T1
var second: T2
public init(a: T1, b: T2) {
first = a
second = b
}
}
// 泛型类实现接口 Eq和PrintSizeable
// 同一个扩展内同时实现多个接口,多个接口之间使用 & 分开,接口的顺序没有先后关系
// 在接口扩展中声明额外的泛型约束。 T1 T2符合Eq接口
//
extend<T1, T2> ExtendPair<T1, T2> <: Eq<ExtendPair<T1, T2>> & Sizeable where T1 <: Eq<T1>, T2 <: Eq<T2> {
// 实现Eq接口的成员函数
public func equals(other: ExtendPair<T1, T2>) {
first.equals(other.first) && second.equals(other.second)
}
// 实现 Sizeable接口的成员属性
public prop size: Int {
get() {
2
}
}
}
class ExtendFoo <: Eq<ExtendFoo> {
public func equals(_: ExtendFoo): Bool {
true
}
}
// 如果被扩展的类型已经包含接口要求的函数或属性,那么在扩展中不需要并且也不能重新实现这些函数或属性
// 因为Array已经有size属性了,所以这里不用再重新写一个实现
extend<T> Array<T> <: Sizeable {}
// 扩展的导出
class MyExtendClass {
public func publicAge() {}
private let privateAge: Int = 20
public func cannotShadow() {}
}
extend MyExtendClass {
public func cannotShadowExtend() {}
}
// 不能在 extend 前面加修饰符
// 在同一个 package 内对同一类型可以扩展多次
extend MyExtendClass {
// 扩展不能遮盖被扩展类型的任何成员
// func cannotShadow() {}
// 扩展也不允许遮盖其它扩展增加的任何成员
// public func cannotShadowExtend() {}
// 只能在本扩展内使用
private func privateFunc() {
// 扩展的实例成员与类型定义处一样可以使用 this,this 的功能保持一致
// 同样也可以省略 this 访问成员。扩展的实例成员不能使用 super。
// 使用this调用publicAge
this.publicAge()
// 扩展不能访问被扩展类型的 private 修饰的成员(意味着非 private 修饰的成员均能被访问)
// 无法调用 privateAge
// this.privateAge
}
// 可以在本包内使用
internal func internalFunc() {}
// 可以在本包以及其他包中本类的子类使用。只能在class中使用
protected func protectedFunc() {}
// 可以在其他包中使用
public func publicFunc() {}
// 只能通过类名访问。它前面可以使用private、internal、protected、public修饰
public static func staticFunc() {}
}
// Sizeable接口的定义 和 被扩展的类型MyExtendClass的定义 都在本包中
extend MyExtendClass <: Sizeable {
public prop size:Int {
get() {
2
}
}
}
struct MyExtendStruct {}
extend MyExtendStruct {
// mut 只能在struct中使用
mut func mutFunc() {}
}
interface MyExtendInterface {}
enum MyExtendEnum {One}
extend MyExtendEnum {
// 只能在本扩展内使用
private func privateFunc() {}
// 可以在本包内使用
internal func internalFunc() {}
// 可以在其他包中使用
public func publicFunc() {}
// 只能通过类名访问。它前面可以使用private、internal、protected、public修饰
public static func staticFunc() {}
}
public func extendDemo() {
let str = "one_two_three"
// 调用扩展方法
str.printSize()
let myList0 = MyList<String>()
println(myList0.orgData())
// 因为 String实现了Equatable,所以这里也可以调用firstEQSecond
myList0.firstEQSecond()
let myList1 = MyList<(Int,Int)>()
// 因为元组(Int, Int) 没实现Equatable,所以这里不能调用firstEQSecond
// myList1.firstEQSecond()
let arr = [1,2,3]
// 这里可以调用printSize,因为上面给Array扩展了PrintSizeable接口
println(arr.size)
let extendsCls = MyExtendsClass()
// 直接扩展中,只有protected和public修饰的函数可以导出
extendsCls.protectedFunc()
extendsCls.publicFunc()
// 因为staticFunc前面使用了public修饰,所以它也被导出了
MyExtendsClass.staticFunc()
// 访问接口扩展的函数g
extendsCls.g()
}
参考资料
- 仓颉编程语言开发指南 developer.huawei.com/consumer/cn…
- 仓颉编程语言白皮书 developer.huawei.com/consumer/cn…
- 仓颉编程语言语言规约developer.huawei.com/consumer/cn…