编程与类型系统03
发表于:2024-06-26 | 分类: 学习

复合类型

元组

  • 元组类型:元组类型由一组类型构成,通过它们在元组中的位置可以访问这些组成类型。元组提供了一种特殊的组数据的方式,允许我们将不同类型的多个值作为一个值进行传递。

    • 基本元组类型:

      1
      2
      3
      4
      5
      type 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
      18
      class 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
      14
      class 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
    24
    class 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
    46
    enum 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 类型包含两个类型,TLeftTRight。约定为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
    30
    class 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
      24
      type 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
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
class Point {
readonly kind: string = "Point";
x: number = 0;
y: number = 0;
}
class Circle extends Point {
readonly kind: string = "Circle";
radius: number = 0;
}

class Rectangle extends Point {
readonly kind: string = "Rectangle";
width: number = 0;
height: number = 0;
}
type Shape = Point | Circle | Rectangle;

let shapes: Shape[] = [new Circle(), new Rectangle()];

for (let shape of shapes) {
switch (shape.kind) {
case "Point":
let point: Point = <Point>shape;
console.log(`Point ${JSON.stringify(point)}`);
break;
case "Circle":
let circle: Circle = <Circle>shape;
console.log(`Circle ${JSON.stringify(circle)}`);
break;
case "Rectangle":
let rectangle: Rectangle = <Rectangle>shape;
console.log(`Rectangle ${JSON.stringify(rectangle)}`);
break;
default:
throw new Error("Unknown");
}
}

访问者模式

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
46
47
48
49
50
type PH = any;
type PC = any;
type TB = any;

interface IVisitor {
Ph(ph: PH): void;
Pc(pc: PC): void;
Tb(tb: TB): void;
}

class Renderer implements IVisitor {
Ph(ph: PH) {}
Pc(pc: PC): void {}
Tb(tb: TB): void {}
}

class ScreenReader implements IVisitor {
Ph(ph: PH) {}
Pc(pc: PC): void {}
Tb(tb: TB): void {}
}

interface IDItem {
accept(v: IVisitor): void;
}

class Pph implements IDItem {
accept(v: IVisitor): void {
v.Ph(this);
}
}
class Pic implements IDItem {
accept(v: IVisitor): void {
v.Pc(this);
}
}

class Tab implements IDItem {
accept(v: IVisitor): void {
v.Tb(this);
}
}

let doc: IDItem[] = [new Pph(), new Pic(), new Tab()];
let renderer: IVisitor = new Renderer();

for (let i of doc) {
i.accept(renderer);
}

访问变体

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Variant<T1, T2, T3> {
readonly v: T1 | T2 | T3;
readonly index: number;
private constructor(v: T1 | T2 | T3, index: number) {
this.index = index;
this.v = v;
}
static make1<T1, T2, T3>(v: T1): Variant<T1, T2, T3> {
return new Variant<T1, T2, T3>(v, 0);
}
static make2<T1, T2, T3>(v: T2): Variant<T1, T2, T3> {
return new Variant<T1, T2, T3>(v, 1);
}
static make3<T1, T2, T3>(v: T3): Variant<T1, T2, T3> {
return new Variant<T1, T2, T3>(v, 2);
}
}

type PH = any;
type PC = any;
type TB = any;
class Renderer {
Ph(ph: PH) {}
Pc(pc: PH) {}
Tb(tb: TB) {}
}

class ScreenReader {
Ph(ph: PH) {}
Pc(pc: PH) {}
Tb(tb: TB) {}
}
function visit<T1, T2, T3>(
variant: Variant<T1, T2, T3>,
func1: (v: T1) => void,
func2: (v: T2) => void,
func3: (v: T3) => void
): void {
switch (variant.index) {
case 0:
func1(<T1>variant.v);
break;
case 1:
func2(<T2>variant.v);
break;
case 2:
func3(<T3>variant.v);
break;
default:
throw new Error();
}
}
class Pph {}
class Pic {}

class Tab {}
let doc: Variant<Pph, Pic, Tab>[] = [
Variant.make1(new Pph()),
Variant.make3(new Tab()),
];
let renderer: Renderer = new Renderer();
for (let i of doc) {
visit(
i,
(ph: PH) => renderer.Ph(ph),
(pc: PC) => renderer.Pc(pc),
(tb: TB) => renderer.Tb(tb)
);
}

代数数据类型

乘积类型

积类型将多个其他类型组合成为一个新类型,其中存储了每个构成类型的值。类型 A、B 和 C 的乘积类型可以写作 A×B×C,它包含 A 中的一个值、B 中的一个值和 C 中的一个值。元组和记录类型都是乘积类型的例子。另外,记录允许我们为每个成员分配有意义的名称

和类型

和类型将多个其他类型组合成为一个新类型,它存储任何一个构成类型的值。类型 A、B 和 C 的和类型可以写作 A + B + C,它包含 A 的一个值,或者 B 的一个值,或者 C 的一个值。可选类型和变体类型是和类型的例子。

上一篇:
编程与类型系统04
下一篇:
编程与类型系统01