复合类型
元组
元组类型:元组类型由一组类型构成,通过它们在元组中的位置可以访问这些组成类型。元组提供了一种特殊的组数据的方式,允许我们将不同类型的多个值作为一个值进行传递。
基本元组类型:
1
2
3
4
5type Point = [number, number]
function distance(point1: Point, point2: Point): number {
return Math.sqrt(
(point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2)
)}自制元组:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Pair<T1, T2> {
m0: T1;
m1: T2;
constructor(m0: T1, m1: T2) {
this.m0 = m0;
this.m1 = m1;
}
}
type Point = Pair<number, number>;
function distance(point1: Point, point2: Point) : number {
return Math.sqrt(
(point1.m0 - point2.m0) ** 2 + (point1.m1 - point2.m1) **2
);
}
为元组赋予意义
记录类型:记录类型与元组类型相似,可将其他类型组合在一起。但是,元组中按照分量值的位置来访问值,而在记录类型中,我们可以为分量设置名称,并通过名称来访问值。在不同的语言中,记录类型被称为记录或者结构。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
function distance(point1: Point, point2: Point) : number {
return Math.sqrt(
(point1.x - point2.x) ** 2 + (point1.y - point2.y) **2
);
}
一般来说,最好定义带命名分量的记录,而不是传递元组。由于元组没有为分量命名,这就有可能错误地解释它们。从效率和功能的角度看,元组并没有比记录多提供什么,只不过我们在使用元组时通常可以内联声明它们,而在使用记录时通常会提供一个单独的定义。在大部分情况下,添加一个单独的定义是值得的,因为这为变量提供了额外的意义。
维护不变量
如果成员是不可变的,就不需要使用函数让它们保证不变量。成员只有在构造时才会设置一次,所以我们可以把所有验证逻辑移动到构造函数中。不可变的数据有其他优势:在不同的线程中并发访问这些数据是安全的,因为数据不会改变。可变性可能导致数据竞争,即一个线程修改了值,而另一个线程正在使用该值。
包含不可变成员的记录的缺点是,每当需要一个新值时,就需要创建一个新实例。根据创建新实例的开销,我们可能选择使用 getter 和 setter 方法来更新记录的成员,也可能选择让每次更新都需要创建一个新对象
使用类型表达多选一
枚举
枚举类型的一个变量可以是提供的值中的任何一个。每当我们有一小组可能的取值,并且想要以不会导致歧义的方式表示它们时,就会使用枚举。
可选类型
可选类型代表另一个类型 T 的可选值。可选类型的实例可以保存类型 T 的一个值(任意值),或者保存一个特殊值来指出不存在类型 T 的值。
自制可选类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Optional<T> {
private value: T | undefined;
private assigned: boolean;
constructor(value?: T) {
if(value) {
this.value = value;
this.assigned = true;
} else {
this.value = undefined;
this.assigned = false;
}
}
hasValue(): boolean {
return this.assigned;
}
getValue(): T {
if(!this.assigned) throw Error();
return <T>this.value;
}
}
结果或错误
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46enum Errors {
OK,
NO_INPUT,
INVALID,
}
enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
}
class Result {
error: Errors;
value: Day;
constructor(error: Errors, value: Day) {
this.error = error;
this.value = value;
}
}
function parseDay(input: string): Result {
if (input === "") {
return new Result(Errors.NO_INPUT, Day.SUNDAY);
switch (input.toLowerCase()) {
case "sunday":
return new Result(Errors.OK, Day.SUNDAY);
case "monday":
return new Result(Errors.OK, Day.MONDAY);
case "tuesday":
return new Result(Errors.OK, Day.TUESDAY);
case "wednesday":
return new Result(Errors.OK, Day.WEDNESDAY);
case "thursday":
return new Result(Errors.OK, Day.THURSDAY);
case "friday":
return new Result(Errors.OK, Day.FRIDAY);
case "saturday":
return new Result(Errors.OK, Day.SUNDAY);
default:
return new Result(Errors.INVALID, Day.SUNDAY);
}
}
}自制 Either 类型
Either 类型包含两个类型,
TLeft和TRight。约定为TLeft存储错误类型,TRight存储有效值类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class Either<TLeft, TRight> {
private readonly value: TLeft | TRight;
private readonly left: boolean;
private constructor(value: TLeft | TRight, left: boolean) {
this.value = value;
this.left = left;
}
isLeft(): boolean {
return this.left;
}
getLeft(): TLeft {
if (!this.isLeft()) throw new Error();
return <TLeft>this.value;
}
isRight(): boolean {
return !this.left;
}
getRight(): TRight {
if (!this.isRight()) throw new Error();
return <TRight>this.value;
}
static makeLeft<TLeft, TRight>(value: TLeft) {
return new Either<TLeft, TRight>(value, true);
}
static makeRight<TLeft, TRight>(value: TRight) {
return new Either<TLeft, TRight>(value, false);
}
}使用 Either 类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type Result = Either<Errors, Day>;
function parseDay(input: string): Result {
if (input === "") {
return Either.makeLeft(Errors.NO_INPUT);
switch (input.toLowerCase()) {
case "sunday":
return Either.makeRight(Day.SUNDAY);
case "monday":
return Either.makeRight(Day.MONDAY);
case "tuesday":
return Either.makeRight(Day.TUESDAY);
case "wednesday":
return Either.makeRight(Day.WEDNESDAY);
case "thursday":
return Either.makeRight(Day.THURSDAY);
case "friday":
return Either.makeRight(Day.FRIDAY);
case "saturday":
return Either.makeRight(Day.SATURDAY);
default:
return Either.makeLeft(Errors.NO_INPUT);
}
}
}
变体
变体类型
变体类型也称为标签联合类型,包含任意数量的基本类型的值。标签指的是即使基本类型有重合的值,我们仍然能够准确说明该值来自哪个类型。
例子:
1 | class Point { |
访问者模式
1 | type PH = any; |
访问变体
1 | class Variant<T1, T2, T3> { |
代数数据类型
乘积类型
积类型将多个其他类型组合成为一个新类型,其中存储了每个构成类型的值。类型 A、B 和 C 的乘积类型可以写作 A×B×C,它包含 A 中的一个值、B 中的一个值和 C 中的一个值。元组和记录类型都是乘积类型的例子。另外,记录允许我们为每个成员分配有意义的名称
和类型
和类型将多个其他类型组合成为一个新类型,它存储任何一个构成类型的值。类型 A、B 和 C 的和类型可以写作 A + B + C,它包含 A 的一个值,或者 B 的一个值,或者 C 的一个值。可选类型和变体类型是和类型的例子。