TypeScript Types:�The First 500 Years
Dan Vanderkam, tsconf 2021
tsconf 2019
Testing Types: An Introduction to dtslint
Effective TypeScript
O'Reilly 2019
What is a type?
Let's go back to the beginning of TypeScript
Let's go (all the way) back to the beginning
of TypeScript
Michelangelo Buonarroti (1475–1564)
Marble quarries of Carrara, Italy
unknown
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!
unknown
David (1504)
MyAPI
DBSchema
HTMLElement
unknown
{} aka Object
undefined
null
object
string
ref: MDN / primitive
boolean
number
symbol
bigint
undefined
null
undefined
null
object
string
ref: MDN / primitive
boolean
number
symbol
bigint
What is a type?
A set of values and the things you can do with them.
Boris Cherny, Programming TypeScript (2019), p. 17
Type
What is a type?
A set of values and the things you can do with them.
Boris Cherny, Programming TypeScript (2019), p. 17
Type
How do mathematicians define sets?
Mr. Euler
Mr. Cantor
Mr. Venn
How do mathematicians define sets?
Mr. Euler
Mr. Cantor
Mr. Venn
{red, green, blue}
{A, B, C}
{1, 2, 3}
How do mathematicians define sets?
Mr. Euler
Mr. Cantor
Mr. Venn
{(x, y) | x2 + y2 < 1}
How do mathematicians define sets?
Mr. Euler
Mr. Cantor
Mr. Venn
Numbers ∪ Letters
✖️
–
object
RegExp
• /abc/
• /def/
Function
• x => x
• x => x**2
Array
• []
• [1, 2, '3']
HTMLElement
• myDiv
• yourDiv
• {name: 'tsconf', year: 2021}
What set did you define?
object
Conference
• {name: 'tsconf', year: 2021}
•
•
•
•
•
•
interface Conference {
name: string;
year: number;
}
let c: Conference = {
name: 'tsconf',
year: 2021,
};
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'
}
✗
✓
✗
✓
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!
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
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;
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,
};
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;
}
✓
✗
✓
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;
}
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"
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:
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
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.
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>
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'];
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';
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
Appendix
Leonardo and Michelangelo