泛型

如果有一个声明,在该声明中使用尖括号声明了类型形参,那么称这个声明是泛型的。在使用泛型声明时,类型形参可以被代换为其他的类型。通过在函数签名中声明类型形参,可以定义一个泛型函数;通过在class、interrace、struct、enum、typealias 定义中声明类型形参,可以定义泛型类型。

类型形参与类型变元

在仓颉编程语言中,用标识符表示类型形参,并用 <> 括起。通过在 <> 内用, 分隔多个类型形参名称,可以提供多个类型形参,如 <T1, T2, T3>,T1, T2, T3 均为类型形参。

一旦声明了类型形参,这些形参就可以被当做类型来使用。

当使用标识符来引用声明的类型形参时,这些标识符被称为类型变元。

类型形参的语法如下:

typeParameters
: '<' identifier (',' identifier)* '>';

泛型约束

在仓颉语言中可以用 <: 来表示一个类型是另一个类型的子类型。通过声明这种关系可以对泛型类型形参声明加以约束,使得它只能被替换为满足特定约束的类型。

泛型约束通过 where 之后的 <: 运算符来声明,由一个下界与一个上界来组成。其中 <: 左边称为约束的下界,下界只能为类型变元。<: 右边称为约束上界,约束上界可以为类型。当一个约束的上界是类型时,该约束为子类型约束。当上界为类型时,上界可以为任何类型。

一个类型变元可能同时受到多个上界约束,对于同一个类型形参的多个上界必须使用 & 连接,以此来简化一个类型形参有多个上界约束的情形,它本质上还是多个泛型约束。对不同类型变元的约束需要使用, 分隔。

声明约束的语法如下:

upperBounds
: type ('&' type)*;
genericConstraints
: 'where' identifier '<:' upperBounds (',' identifier '<:' upperBounds)*;

例如以下示例展示了泛型约束声明的写法:

interface Enumerable<U> where U <: Bounded {...}
interface Comparable<T> {...}
func collectionCompare<T>(a: T, b: T) where T <: Comparable<T> & Seqence {...}func sort<T, V>(input: T)
where T <: Enumerable<V>, V <: Object {...}

类型变元 X 和 Y 的约束相同,指的是所有满足 X 约束的类型都满足 Y 的约束,且所有满足 Y 的约束的类型也都满足 X 的约束;

类型变元 X 比 Y 的约束更严格,指的是所有满足 X 约束的类型都满足 Y 的约束,反之,不一定满足;

如果 X 比 Y 的约束更严格,则 Y 比 X 的约束更宽松。

两个泛型类型的约束相同,指的是泛型类型的类型变元数量相同,且所有对应的类型变元约束均相同;

一个泛型类型 A 的约束比另一个泛型类型 B 的约束更严格,指的是 A 和 B 的类型变元数量相同,且 A 的所有类型变元的约束均比 B 中对应的类型变元更严格;

一个泛型类型 A 的约束比另一个泛型类型 B 的约束更严格,则 B 的约束比 A 更宽松。

比如下面的两个泛型 C 和 D ,假设有 I1 <: I2,C 的约束比 D 更严格

class C<X, Y> where X <: I1, Y <: I1class D<X, Y> where X <: I2, Y <: I2

类型型变

在正式介绍泛型函数与泛型类型前,先简单介绍以下类型型变,以此来说明在仓颉编程语言中,泛型类型的子类型关系。

定义

如果 A 和 B 是类型,T 是类型构造器,设其有一个类型参数 X,那么:

• 如果 T(A) <: T(B) 当且仅当 A <: B,则 T 在 X 处是协变的。• 如果 T(A) <: T(B) 当且仅当 B <: A,则 T 在 X 处是逆变的。• 如果 T(A) <: T(B) 当且仅当 A = B,则 T 是不型变的。

泛型不型变

在仓颉编程语言中,所有的泛型都是不型变的。这意味着如果 A 是 B 的子类型,ClassName 和ClassName 之间没有子类型关系。我们禁止这样的行为以保证运行时的安全。

函数类型的型变

函数的参数类型是逆变的,函数的返回类型是协变的。假设存在函数 f1 的类型是 S1 -> T1,函数 f2的类型是 S2 -> T2。如果 S2 <: S1 并且 T1 <: T2,则 f1 的类型是 f2 的类型的子类型。

元组类型的协变

元组之间是存在子类型关系的,如果一个元组的每一个元素都是另一个元组的对应位元素的子类型,则该元组是另一个元组的子类型。假设有元组 Tuple1 和 Tuple2,它们的类型分别为 (A1, A2…, An)、(B1,B2…, Bn),如果对于所有 i 都满足 Ai <: Bi,则 Tuple1 <: Tuple2。

型变的限制

现在以下两种情况的型变关系被禁止:

  1. class 以外的类型实现接口,该类型和该接口之间的子类型关系不能作为协变和逆变的依据。2. 实现类型通过扩展实现接口,该类型和该接口之间的子类型关系不能作为协变和逆变的依据。

这些限制除了影响型变关系以外,同时也会影响 override 对于子类型的判定。不满足型变关系的类型,在发生 override 时不能作为子类型的依据。

interface I {
func f(): Any
}
class Foo <: I {
func f(): Int64 { // error...
}
}

泛型约束上界中导出的约束

对于一个约束 L <: T<T1…Tn>,其中的上界 T<T1…Tn> 的声明 T 的类型形参可能还需要满足一些约束,在实参 Ti 的代换后,这些约束需要被隐式地引入到当前声明的上下文中。例如:

interface Eq<T> {
func eq(other: T): Bool
}
interface Ord<T> where T <: Eq<T> {func lt(other: T): Bool
}
func foo<T>(a: T) where T <: Ord<T> {a.eq(a)
a.lt(a)
}

对于 foo 函数,虽然只声明了 T 受到 Ord 的约束,但是由于 Ord 的 T 类型受到了 Eq 的约束,所以在foo 函数里是可以使用 Eq 中的 eq 函数的。这样,foo 函数的约束实际上是 T <: Eq & Ord。这样在声明一个泛型参数满足一个约束时,这一约束的上界中需要满足的约束也将被引入。
对于其他泛型声明,隐式地引入约束上界的约束这一规则也是有效的。例如

interface A {}
class B<T> where T <: A {}
class C<U> where U <: B<U> {} // actual constraints are U <: A & B<U>

这里,对于类 C,它的泛型形参 U 所受到的约束实际上为 U <: A & B

注意:虽然当前声明中上界的约束会被隐式地引入,但当前声明仍然可以将这些约束显式地写出。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐