mmap.page

Notes on TypeScript

Top and Bottom Types

Unlike Anything or Any in most static typed languages, any supports the same operations as a value in JavaScript and minimal static type checking is performed. TypeScript's unknown is more similar to Any (top type) in other languages.

TypeScript's bottom type is never, which is called Nothing in some languages.

ts
const verifyCasesAreExhaustive = (kind: "x" | "y" | "z") => {
switch (kind) {
    case "x":
        return 1
    case "y":
        return 2
    case "z":
        return 3
    default:
        const nothing: never = kind
        throw(nothing)
}
}

Function Overloads

Considering the following definition:

typescript
function plus(x: number, y: 0): boolean;
function plus(x: number, y: number): number;
function plus(x: string, y: string): string;
// function plus(x: number, y: string): boolean;
// function plus(x: string, y: number): boolean;
function plus(x: number | string, y: number | string): number | string | boolean {
    if (typeof x == "number" && typeof y == "number") {
        if (y === 0) {
            return false
        } else {
        return x + y;
        } 
    } else if (typeof x == "string" && typeof y == "string") {
        return `${x}${y}`; // equivalent to `x + y`.
    } else {
        return false;
    }
}

In early versions of TypeScript (e.g. [v3.5.1]),
the following code gives misleading error information:

```typescript
plus("1", 0)
// error: Argument of type '0' is not assignable to parameter of type 'string'.

Readers may wonder why TypeScript does not match ("1", 0) against plus(x: number | string, y: number | string).

However, recent versions of TypeScript (e.g. v5.3.2) provides clearer message:

typescript
plus("1", 0)
// The call would have succeeded against this implementation,
// but implementation signatures of overloads are not externally visible

Type Equality

With TypeScript's conditional types, testing type equality is intuitive:

ts
type EqEq<T, S> = [T] extends [S] ? ([S] extends [T] ? true : false) : false

type FunctionOverloadsEquality = EqEq<
    { (x: 0, y: null): void; (x: number, y: null): void },
    { (x: number, y: null): void; (x: 0, y: null): void }> // true

type F = (x: 0, y: null) => void
type G = (x: number, y: string) => void
type FunctionIntersectionEquality = EqEq<F & G, G & F> // true

The Crazy satisfies Operator

Below is the example given in What's New in TypeScript 4.9:

ts
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ The typo is now caught!
} satisfies Record<Colors, string | RGB>;
// Both of these methods are still accessible!
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

IMHO, this shows the craziness of TypeScript. Why green is encoded as hex string but red and blue are encoded as RGB arrays? What if one day someone refactors the code and change green to an RGB array? And in real world application, I would rather make all colors encoded as hex strings or RGB arrays, but not a mix of them. Then the getComponent or displayHex will be implemented assuming input is either a hex string or an RGB array.