46-泛型-枚举-子类型关系
泛型枚举
在仓颉编程语言中,泛型 enum 声明的类型里被使用得最广泛的例子之一就是 Option
类型了,关于 Option
详细描述可以详见 Option 类型章节。 Option
类型是用来表示在某一类型上的值可能是个空的值。这样,Option
就可以用来表示在某种类型上计算的失败。这里是何种类型上的失败是不确定的,所以很明显,Option
是一个泛型类型,需要声明类型形参。
package core // `Option` is defined in core.
public enum Option<T> {
Some(T)
| None
public func getOrThrow(): T {
match (this) {
case Some(v) => v
case None => throw NoneValueException()
}
}
...
}
可以看到,Option<T>
分成两种情况,一种是 Some(T)
,用来表示一个正常的返回结果,另一种是 None
用来表示一个空的结果。其中的 getOrThrow
函数会是将 Some(T)
内部的值返回出来的函数,返回的结果就是 T
类型,而如果参数是 None,那么直接抛出异常。
例如:如果我们想定义一个安全的除法,因为在除法上的计算是可能失败的。如果除数为 0,那么返回 None
,否则返回一个用 Some
包装过的结果:
func safeDiv(a: Int64, b: Int64): Option<Int64> {
var res: Option<Int64> = match (b) {
case 0 => None
case _ => Some(a/b)
}
return res
}
这样,在除数为 0 时,程序运行的过程中不会因除以 0 而抛出算术运算异常。
泛型类型的子类型关系
实例化后的泛型类型间也有子类型关系。例如当我们写出下列代码时,
interface I<X, Y> { }
class C<Z> <: I<Z, Z> { }
根据第 3 行,便知 C<Bool> <: I<Bool, Bool>
以及 C<D> <: I<D, D>
等。这里的第 3 行可以解读为“于所有的(不含类型变元的) Z
类型,都有 C<Z> <: I<Z, Z>
成立”。
但是对于下列代码
open class C { }
class D <: C { }
interface I<X> { }
I<D> <: I<C>
是不成立的(即使 D <: C
成立),这是因为在仓颉语言中,用户定义的类型构造器在其类型参数处是不型变的。
型变的具体定义为:如果 A
和 B
是(实例化后的)类型,T
是类型构造器,设有一个类型参数 X
(例如 interface T<X>
),那么
- 如果
T(A) <: T(B)
当且仅当A = B
,则T
是不型变的。 - 如果
T(A) <: T(B)
当且仅当A <: B
,则T
在X
处是协变的。 - 如果
T(A) <: T(B)
当且仅当B <: A
,则T
在X
处是逆变的。
因为现阶段的仓颉中,所有用户自定义的泛型类型在其所有的类型变元处都是不变的,所以给定 interface I<X>
和类型 A
、B
,只有 A = B
,我们才能得到 I<A> <: I<B>
;反过来,如果知道了 I<A> <: I<B>
,也可推出 A = B
(内建类型除外:内建的元组类型对其每个元素类型来说,都是协变的;内建的函数类型在其入参类型处是逆变的,在其返回类型处是协变的。)
不型变限制了一些语言的表达能力,但也避免了一些安全问题,例如“协变数组运行时抛异常”的问题(Java 便有这个问题)。