1 of 38

TypeScript Types:�The First 500 Years

Dan Vanderkam, tsconf 2021

tsconf 2019

Testing Types: An Introduction to dtslint

Effective TypeScript

O'Reilly 2019

2 of 38

What is a type?

3 of 38

Let's go back to the beginning of TypeScript

4 of 38

Let's go (all the way) back to the beginning

of TypeScript

Michelangelo Buonarroti (1475–1564)

5 of 38

Marble quarries of Carrara, Italy

6 of 38

7 of 38

unknown

8 of 38

Michelangelo knew just what to do

Michelangelo Buonarroti (1475–1564)

"Every block of stone has a statue inside it and it is the task of the sculptor to discover it."

disclaimer: Michelangelo probably never said this!

9 of 38

unknown

David (1504)

MyAPI

DBSchema

HTMLElement

10 of 38

unknown

11 of 38

{} aka Object

undefined

null

12 of 38

object

string

boolean

number

symbol

bigint

undefined

null

13 of 38

undefined

null

object

string

boolean

number

symbol

bigint

14 of 38

What is a type?

A set of values and the things you can do with them.

Boris Cherny, Programming TypeScript (2019), p. 17

Type

15 of 38

What is a type?

A set of values and the things you can do with them.

Boris Cherny, Programming TypeScript (2019), p. 17

Type

16 of 38

How do mathematicians define sets?

Mr. Euler

Mr. Cantor

Mr. Venn

17 of 38

How do mathematicians define sets?

  • Constructively (by listing their members)

Mr. Euler

Mr. Cantor

Mr. Venn

{red, green, blue}

{A, B, C}

{1, 2, 3}

18 of 38

How do mathematicians define sets?

  • Constructively (by listing their members)
  • Through a membership test (is this value in the set or is it not?)

Mr. Euler

Mr. Cantor

Mr. Venn

{(x, y) | x2 + y2 < 1}

19 of 38

How do mathematicians define sets?

  • Constructively (by listing their members)
  • Through a membership test (is this value in the set or is it not?)
  • In terms of other sets (via operations like union, intersection, difference, etc.)

Mr. Euler

Mr. Cantor

Mr. Venn

Numbers ∪ Letters

✖️

20 of 38

object

RegExp

/abc/

/def/

Function

x => x

x => x**2

Array

[]

[1, 2, '3']

HTMLElement

myDiv

yourDiv

{name: 'tsconf', year: 2021}

21 of 38

What set did you define?

object

Conference

{name: 'tsconf', year: 2021}

interface Conference {

name: string;

year: number;

}

let c: Conference = {

name: 'tsconf',

year: 2021,

};

22 of 38

What set did you define?

interface Conference {

name: string;

year: number;

}

← 1. Is it an object?

← 2. Does it have a name property that's a string?

← 3. Does it have a year property that's a number?

This interface defines a three part test:

That's it!

{

name: 'tsconf',

year: 2021

}

{

name: 'jsconf',

year: 2021,

locale: 'Europe',

}

'tsconf'

{

name: 'tsconf',

year: 'this one'

}

23 of 38

What set did you define?

interface Conference {

name: string;

year: number;

}

← 1. Is it an object?

← 2. Does it have a name property that's a string?

← 3. Does it have a year property that's a number?

This interface defines a three part test:

That's it!

{

name: 'tsconf',

year: 2021

}

{

name: 'jsconf',

year: 2021,

locale: 'Europe',

}

#12936: Exact Types (542 👍s)

To the playground!

24 of 38

Type Inference

let x = 10;

Every symbol in TypeScript has a type at every location.

What should the type of x be?

In other words: what is the set of possible values for x?

Which of these should be allowed?

let x = 10;

x = 12;

number

10

12

let x = 10;

x = 'twelve';

number|string

10

'twelve'

let x = 10;

x = null;

number|null

10

null

10

number

number|string

number|null

25 of 38

Type Inference and Objects

let c = {

name: 'tsconf',

year: 2021,

};

What should the type of c be here? What of these should be allowed?

c.year = 2022;

c.year = 'this year';

c.isOnline = true;

26 of 38

Type Inference and Objects

What should the type of c be here? What of these should be allowed?

c.year = 2022;

c.year = 'this year';

c.isOnline = true;

Type declarations and annotations let you encode intent.

interface Conference {

name: string;

year: number;

}

let c: Conference = {

name: 'tsconf',

year: 2021,

};

27 of 38

Type Inference and Objects

let c: Conference = {

name: 'tsconf',

year: 2021,

};

What should the type of c be here? What of these should be allowed?

c.year = 2022;

c.year = 'this year';

c.isOnline = true;

Type declarations and annotations let you encode intent.

interface Conference {

name: string;

year: number | string;

}

28 of 38

Type Inference and Objects

What should the type of c be here? What of these should be allowed?

c.year = 2022;

c.year = 'this year';

c.isOnline = true;

Type declarations and annotations let you encode intent.

interface Conference {

name: string;

year: number;

isOnline?: boolean;

}

let c: Conference = {

name: 'tsconf',

year: 2021,

};

interface Conference {

name: string;

year: number;

[others: string]: unknown;

}

29 of 38

Types may not always feel very precise

function getRand() {

return Math.floor(500 * Math.random());

}

What should the return type of getRand be?

It can't return just any number.

Can't return a negative number.

Can't return a number greater than (or equal to) 500.

Has to return an integer.

We can't easily represent ranges in a type (#15480: Suggestion: Range as Number type, 850 👍s)

So number it is!

number

"[Use] the most specific type that works and is convenient for your use case."

- Jesse Hallett, 2019, r/typescript comments on "When to use `never` and `unknown` in TypeScript"

30 of 38

Soundness and Unsoundness

When the static type and reality diverge (a runtime value is outside the set of allowed values) , we have unsoundness.

const xs = [10, 20, 30];

let x = xs[0]; // TS infers number type

How should we think about the types here?

ref: "The Seven Sources of Unsoundness in TypeScript", effectivetypescript.com

number

10

20

30

undefined

x = xs[2];

x = xs[3];

x.toString(); // uh-oh!

x = xs[1];

The history of TypeScript is filled with new features designed to fix soundness holes:

  • --strictNullChecks
  • --noUncheckedIndexedAccess
  • --exactOptionalPropertyTypes
  • etc.

31 of 38

Mapping between types

type ABC = 'a' | 'b' | 'c';

type BCD = 'b' | 'c' | 'd';

type U = ABC | BCD; // 'a' | 'b' | 'c' | 'd'

type I = ABC & BCD; // 'b' | 'c'

Mathematicians like to define sets in terms of other sets.

We do this in TypeScript with type operators and generics.

'a'

'b' 'c'

'd'

ABC

DEF

32 of 38

Mapping between types

interface ABC {

a: string;

b: number;

c: number;

}

type K = keyof ABC; // type is 'a' | 'b' | 'c'

type V = ABC[K]; // type is string | number

type V2 = ABC['b' | 'c']; // type is number

Mathematicians like to define sets in terms of other sets.

We do this in TypeScript with type operators and generics.

33 of 38

Mapping between types

Mathematicians like to define sets in terms of other sets.

We do this in TypeScript with generics and type operations.

type MakeFour<T> = [T, T, T, T];

David

MakeFour<David>

34 of 38

Ever finer cuts

Array

Since forever:

let nums: number[] = [1, 2];

Since TypeScript 1.3 (2014):

let a: [number, string] = [1, 'two'];

Since TypeScript 3.0 (2018):

let a: [number, ...string[]] = [1, 'two', 'three'];

Since TypeScript 4.0 (2020):

let a: [...number[], string] = [1, 2, 3, 'four'];

35 of 38

Ever finer cuts

string

Since TypeScript 1.8 (2016):

type T = 'A' | 'C' | 'G' | 'T';

let c: T = 'A';

c = 'U'; // Error!

Since TypeScript 4.1 (2020):

type Color = `rgb(${number},${number},${number})`;

let c: Color = 'rgb(255,0,255)';

c = 'rgb(255,255)'; // Error!

Since forever:

let c: string = 'A';

c = 'U';

36 of 38

Let's give Michelangelo the last word

"The marble not yet carved can hold the form of every thought the greatest artist has."

unknown contains all your past and future types.

Go carve them out!

Effective TypeScript

O'Reilly 2019

effectivetypescript.com

unknown

37 of 38

Appendix

38 of 38

Leonardo and Michelangelo