Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mheiber/fba345671be181f303adf7101c6342ae to your computer and use it in GitHub Desktop.
Save mheiber/fba345671be181f303adf7101c6342ae to your computer and use it in GitHub Desktop.
2019 Conference Talk: TypeScript's Type System as a Little Language

TypeScript's Type System as a Little Language

Theme: Thinking of type-level TS as a programming language

It has all the pieces!

  • data:
    • objects, arrays, and tuples
  • functions
  • structured programming:
    • conditionals
    • pattern matching
    • recursion

Goals of this talk

  • fun
  • familiarity with type-level programming in TS
  • knowing the limits
  • seeing beyond the limits

Goal 1: Familiarity

  • Analogies can be helpful learning aids:

analogy

Goal 2: Know the Limits of type-level TS

  • The programming language metahpor can help us:
    • guess when TS can solve a poblem
    • guess when TS probably can't

xkcd_cs_tasks_easy_vs_hard/

Goal 3: See Beyond the Limits?

  • What would type-level TS look like if it was even more like a normal programming language?

How is type-level TS like a programming language?

Constants

type Id = number;

Compare to:

const id1 = 1;

Numbers, Strings, arrays

// types
type Id = number | string;
type Ids = Id[];

// values
const ids: Id[] = [1, "abc2"];

Objects and Tuples

type Person = {
  name: string;
  height: number;
};

type Name = Person["name"];

// values
const me: Person = { name: "max", height: 2 };
const myName: string = me.name;
const myFirstName: Person["name"] = me.name;

Objects and Tuples

type Person = {
  name: [string, string]
  height: number;
};

type Name = Person["name"];

// values
const me: Person = { name: ["max", "heiber"], height: 2 };
const wilson: Person = { name: ["w.", "w.", "wilson" ], height: 2 }; // error

const myFirstName: Person["name"] = me.name;

Functions

// definition
type Timed<T> = { value: T; start: Date };

// apply the function
type TimedRoute = Timed<Route>

// type { value: Route; start: Date };

Functions

Notice how similar type-level TS is to programming with values:

// type-level
type Timed<T> = { value: T; start: Date };

// valuelevel
const addStartTime = (value) => ({ value, start: new Date() })

Mapping and Conditionals

type GraphQLPerson = {
  name: string;
  height: () => number;
};

type UnFunc<T> = { [P in keyof T]: T[P] extends Function ? undefined : T[P] };

type UnFuncyPerson = UnFunc<GraphQLPerson>;
const regularMax: UnFuncyPerson = { name: "max", height: undefined };

Mapping and Conditionals

type UnFunc<T> = { [P in keyof T]: T[P] extends Function ? undefined : T[P] };
  • Not the easiest to read
  • But compare to value-level coding!
function unFunc(obj) {
  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => {
      return typeof v === "function" ? [k, undefined] : [k, v];
    })
  );
}

Pattern Matching

value-level pattern matching

const obj = { a: 3 };
const { /* infer */ a = undefined } = obj;
a; // 3

type-level pattern matching

// definition
type ParamsOf<T extends Function> = T extends (...args: infer P) => any
  ? P
  : unknown;

// usage
declare function foo(a: string, b: number): void;
type FooParams = ParamsOf<typeof foo>; // type [string, number]

type-level TS

It's like a tiny, pure version of JS!

  • with data, functions, and structured programming

Goals of the talk

  • Familiarity with type-level TS as a language
    • objects, functions, conditionals, etc. etc.
  • Know the Limits
  • See beyond the limits

Goals of the talk

  • Know the Limits:
    • What is impossible?
    • What is awkward?

Can We Type It?

get function similar to Lodash.get:

const person = {
    name: 'max'
    address: {
        street: 'Main St.'
        postCode: 06057
    }
}
const streetName: string = get(person, 'address', 'street')
  • YES
  • NO

Can We Type It?

type-level addition

declare const num: Add<One, Two>;
const two: Two = num; // error
const three: Three = num;
  • YES
  • NO

Can We Type It?

type-level addition: YES

type Add<N1 extends Num, N2 extends Num> = {
  base: N2;
  recur: Add<Minus1<N1>, Plus1<N2>>;
}[N1 extends Zero ? "base" : "recur"];

adapted from microsoft/TypeScript#14833

Can We Type It?

Can we teach TS to understand the relationship between

  • array.length
  • array.pop()
const array: number[] = [1, 2, 3]
const f = (n: number) => { }
while (array.length) {
    f(array.pop())
    // error: 'undefined' is not assignable to 'number'
}
  • YES
  • NO

Can We Type It?

const thing = createThing();
thing.doStuff(); // Error: `thing` is not initialized


await thing.init();
thing.doStuff();
  • YES
  • NO

Limitations of type-level TS:

Mismatch: - Using immutable types to model mutable values`

Solutions: - this-aware type narrowing?

Limitations of type-level TS:

Programming in the type system can be awkward! (compared to value-level programming)

type Add<N1 extends Num, N2 extends Num> = {
  base: N2;
  recur: Add<Minus1<N1>, Plus1<N2>>;
}[N1 extends Zero ? "base" : "recur"];

Why is type-level programming sometimes awkward?

Imagine your favorite functional programming language MINUS:

  • local variables
  • functions as parameters
  • functions as return values

What could type-level programming in TS look like?

  • with local variables, first-class type-level functions, etc.?
typeFunc Add<N1 extends Num, N2 extends Num> {
    if (N1 extends Zero) {
        return N2
    }
    type NewN1 = Minus1<N1>
    type NewN2 = Plus1<N2>
    return Add<NewN1, NewN2>
}

What would type-level programming in TS look like

  • with local variables, first-class type-level functions, etc.?

Value-level bind:

function bind<U extends any[]>(f extends (...origArgs: any[]) => any, ...boundArgs: U) {
    return (...args) => f(...boundArgs, ...args)
}
const add1 = bind(add, 1)

Type-level bind:

typeFunc Bind<F: (<...Any[] => Any) ...BoundArgs: Any[]> {
    return <...Args> => F(...BoundArgs, ...Args)
}
type Add1 = Bind<Add, One>

Takeaways:

  • Type-level programming is just programming
  • Most JS code can be decently well-typed
  • The occasional awkwardness comes from simplicity, not complexity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment