# <Programming with Types>随想: Chapter 2. Basic types

> 类型限制了一个变量可以接受的**有效值的集合**，对数据可以进行的操作，数据的意义。

# 空类型(The empty type)

根据类型的定义，类型定义了可以接受的有效值集合，那么这个集合有没有可能为空？答案是有可能的，TypeScirpt 的 `never` 就是这种类型。

需要注意的是，空类型不同与 `void`, 后者是有效值集合当中只有一个值，但这个值没有任何意义。而空类型的有效值集合本身是空的。

## 使用场景

### 控制流分析

在函数调用时，标志一个函数不会返回任何值: 在调用过程中抛出异常、死循环或者程序崩溃。即返回值类型为 `never` 的函数，不会将**CPU的控制权**返回给被调函数。

```typescript
function raise(message: string): never {
    throw new Error(message);
}

function forever(): never {
    while (true) {}
}
```

如果返回实际的值或者在调用处后面的继续编写代码逻辑，会引起编译错误。

```typescript
function main() {
    forever()
    console.log(1); // Unreachable code detected.(7027)
}
```

### ADT 的 Exhaustive(穷尽) Check

当业务中有一个 `union type`

```typescript
interface Foo {
  type: 'foo'
}

interface Bar {
  type: 'bar'
}

type All = Foo | Bar
```

在 switch 当中判断 type，TS 是可以收窄类型的 (discriminated union)：

```typescript
function handleValue(val: All) {
  switch (val.type) {
    case 'foo':
      // 这里 val 被收窄为 Foo
      break
    case 'bar':
      // val 在这里是 Bar
      break
    default:
      // val 在这里是 never
      const exhaustiveCheck: never = val
      break
  }
}
```

注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显式声明为 never 的变量。如果一切逻辑正确，那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型：

```typescript
type All = Foo | Bar | Baz
```

然而他忘记了在 handleValue 里面加上针对 Baz 的处理逻辑，这个时候在 default branch 里面 val 会被收窄为 Baz，导致无法赋值给 never，产生一个编译错误。所以通过这个办法，你可以确保 handleValue 总是穷尽 (exhaust) 了所有 All 的可能类型。

参考链接：[https://www.zhihu.com/question/354601204](https://www.zhihu.com/question/354601204)

### 类型运算

`never` 是任何类型的 `subtype`(子类型), 除了never，没有其他类型是never的subtype。

这个例子体现 `never` 的取值有效集是空集，在 TS 中，可以对类型做集合的交并运算。 `never` 是 `union(并集)` 运算的幺元，`intersection(交集)`运算的零元.

```typescript
T | never // 结果为T
T & never // 结果为never
```

# 单元类型(unit type)

单元类型的取值有效集集合中，只有一个值, 因此编译器不需要处理单元类型的具体值。

常见的单元类型有

```typescript
// undefined类型。
let a: undefined = undefined;
// null类型。
let b: null = null;
// unique symbol类型。
const c: unique symbol = Symbol();
// void类型。
let d: void = undefined;
// 字面量类型。
const e: 'hello' = 'hello';
// 联合枚举成员类型。
enum Foo { A, B }
const f: Foo.A = Foo.A;
```

也有人认为 [void is not a unit type in TypeScript](https://sitr.us/2021/12/04/void-is-not-a-unit-type.html)

## 使用场景

当一个函数返回值为单元类型时，这个值没有任何含义，因为无论函数内部发生什么，正常执行都会返回这个类型，并且只有该类型只有一个值。

这种没有返回任何有意义值的函数，通常被称为`actions` 或者 `consumers`, 他们存在的目的是为了产生副作用，对现实世界产生影响，例如 `console.log()`

# Boolean类型

`boolean` 类型存在短路逻辑，如果第一个操作数(`operand`)提供了足够的信息评估(`evaluate`)整个表达式，那么第二个操作数就不会被 evaluate.

# 数字类型

基础的二进制编码，补码，IEEE754方式，此处略去。

# 编码字符串

Unicode编码有几个相似但是截然不同的概念:

* `字符(Characters)`: 文本的计算机表示, 警官emoji(👮)，零宽字符，女性标志(🚺)都是字符。字符这个词在不同的上下文环境中经常会被赋予不同的意思。
    
* `字位(graphemes)`: 用户看到的符号，在渲染文本时，我们使用的是字位(graphemes)，我们不想把一个多字的字位(multiple-character grapheme)拆开。
    
* `glygh(字形)`: "字形 "是一个字符的特殊表现形式。"**C**"（粗体）和 "*C*"（斜体）是 "C "字的两种不同的视觉表现形式。
    
* `code point(码点)` 是文本信息的原子单位。文本是一连串的代码点。每个代码点是一个数字，由Unicode标准赋予其意义。
    
* `code unit(码元)` 编码组成 `code point(码点)` 的一部分. In UTF-8 this means 8 bits, in UTF-16 this means 16 bits. A single code unit may represent a full code point, or part of a code point. For example, the snowman glyph (☃) is **a single code point** but **3 UTF-8 code units**, and **1 UTF-16 code unit**.
    

详细可以参考: [https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme](https://stackoverflow.com/questions/27331819/whats-the-difference-between-a-character-a-code-point-a-glyph-and-a-grapheme)

## 长度可变的编码方式

`UTF-32` 是一种长度不可变的字符串编码方式，对于每一个`grapheme`都使用，32bit存储，但是会造成格外的空间浪费。

`UTF-8` 和 `UTF-16` 是长度可变的编码方式(variable-length encodings).
