TYPES ARE
GREAT?
LET'S TRY A...
REAL WORLD EXAMPLE
LET'S USE TYPESCRIPT TO...
FETCH DATA
OUR EXAMPLE:
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
// server.ts
function appGet(
url: string,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: string): Promise<any>;
QUICK TIP: DECLARE & THEN USE!
// client.ts
declare function fetch(url: string): Promise<any>;
// client-usage.ts
const users: User[] = await fetch("/users")
LESSON LEARNED
USE DECLARE 1st
FEW EXAMPLE USAGES:
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch(42)
const users: User[] = await fetch({ users: true})
LET'S COMPILE!
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch(42)
// => ERROR: number is not assignable to string
const users: User[] = await fetch({ users: true})
// => ERROR: object is not assignable to string
❌
✅
✅
CAN WE SHIP IT?
🥺
✅ compiles
❔ it runs?
TypeError: failed to fetch
✅ compiles
❌ it does not run
🤔
TYPES ARE
💩
REDUCE CODEBASE BY 25%
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
DON'T USE TYPES
THE END
HAVE A GREAT COFFE BREAK!
WAIT A MOMENT
✋
IT'S NOT SO EASY...
LET'S TAKE A STEP BACK:
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const users: User[] = await fetch("/user")
const todos: Todo[] = await fetch("/users")
✅
✅
LET'S TAKE A STEP BACK:
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
// server.ts
function appGet(
url: string,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: string): Promise<any>;
THE SYLVESTER THE CAT PROBLEM:
❌
"Sylvester is a cat, a cat is not Sylvester"
THE URL PROBLEM:
❌
"URL are strings, any string is not a valid URL"
URL
string
USING STRICTER TYPES:
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
type URL = "/users" | "/todos"
// server.ts
function appGet(
url: URL,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: URL): Promise<any>;
USING STRICTER TYPES:
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
const users: User[] = await fetch("/user")
// => ERROR: "/user" is not assignable to "/users" | "/todos"
✅
✅
❌
LESSON LEARNED
OOTB TYPES
ARE TOO WIDE
LESSON LEARNED
DOMAIN TYPES
ARE FUNDAMENTAL
CAN WE SHIP IT?
🥺
✅ compiles
❔ it runs?
title is undefined
✅ compiles
❌ it does not run
0
Advanced issues found▲
🤔
WHAT'S THE PROBLEM?
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
✅
✅
🐰 "Uhm, what's up doc?"
LESSON LEARNED
ALL HUMANS MAKE MISTAKES
LOWERING THE ERROR POSSIBILITIES
// client-usage.ts
const users: User[] = await fetch("/users")
const todos: Todo[] = await fetch("/todos")
const todos: Todo[] = await fetch("/users")
USING STRICTER TYPES:
// common.ts
interface User { id: number, name: string }
interface Todo { id: number, title: string }
type URL = "/users" | "/todos"
// server.ts
function appGet(
url: URL,
fn: (req: Request) => Promise<any>): void;
// client.ts
function fetch(url: URL): Promise<any>;
USING STRICTER TYPES:
// server.ts
function appGet(
url: "/users",
fn: (req: Request) => Promise<User[]>): void;
function appGet(
url: "/todos",
fn: (req: Request) => Promise<Todo[]>): void;
/* ... */
// client.ts
function fetch(url: "/users"): Promise<User[]>;
function fetch(url: "/todos"): Promise<Todo[]>;
/* ... */
WHAT IF... THE INFERENCE JUST KNOWS IT?
// client-usage.ts
const users = await fetch("/users")
const todos = await fetch("/todos")
const todos = await fetch("/users")
😍 "Look Mum! No types!"
IS IT REALLY DRY?
// server.ts
function appGet(
url: "/users",
fn: (req: Request) => Promise<User[]>): void;
function appGet(
url: "/todos",
fn: (req: Request) => Promise<Todo[]>): void;
function appGet(
url: "/tacos",
fn: (req: Request) => Promise<Taco[]>): void;
// client.ts
function fetch(url: "/users"): Promise<User[]>;
function fetch(url: "/todos"): Promise<Todo[]>;
function fetch(url: "/tacos"): Promise<Taco[]>;
TYPE-LEVEL FUNCTIONS MEANS...
TYPE-LEVEL PROGRAMMING
JavaScript
on v8 in the browser or NodeJS
Types
on the type-system in the editor or the compiler
My editor is yelling at me
TAKING A STEP BACK...
TYPESCRIPT FEATURES
STRING LITERALS
type UsersURL = "/users"
// correct assignment
const users: UsersURL = "/users"
// incorrect assignment
const users: UsersURL = "/todos"
// string is not /users statically
const stringVar: string = "/users"
const users: UsersURL = stringVar
OBJECT TYPES (OR INTERFACES)
type User = {
id: number,
name: string
surname: string
age: number
prefix: "mr." | "mss."
}
KEY OF
type User = {
id: number,
name: string
surname: string
age: number
prefix: "mr." | "mss."
}
type A = keyof User;
// => id | name | surname | age | prefix
PROPERTY TYPE ACCESSING
type A = User["name"];
// => string
type B = User["age"];
// => number
type C = User["name" | "age"];
// => string | number
something 🐟y is about to happen
🤔
LET'S CHANGE OUR DOMAIN TYPES
type RoutesMap = {
"/users": User[],
"/todos": Todo[]
}
type URL = keyof RoutesMap
🎉 OUR FIRST TYPE-LEVEL FUNCTION! 🎉
type RoutesMap = {
"/users": User[],
"/todos": Todo[]
}
type URL = keyof RoutesMap
type GetResponseType<U extends URL> = RoutesMap[U]
type A = GetResponseType<"/users">
// => User[]
type B = GetResponseType<"/todos">
// => Todo[]
type C = GetResponseType<"/batman">
// => ERROR: batman is not a valid URL
// server.ts
function appGet<U extends URL>(
url: U,
fn: (req: Request) => Promise<GetResponseType<U>>
): void;
// client.ts
function fetch<U extends URL>(url: U): Promise<GetResponseType<U>>;
🎉 OUR FIRST TYPE-LEVEL FUNCTION! 🎉
IN THE END...WE DID IT!
// client-usage.ts
const users = await fetch("/users")
// => User[]
const todos = await fetch("/todos")
// => Todo[]
const todos = await fetch("/users")
// => User[]
😍 "Look Mum! 25% of code reduction!"
WHAT IF...
PARAMETRIZED URLs
PARAMETRIZED URLs
// client-usage.ts
const john = await fetch("/users/1")
const mattia = await fetch("/users/2")
const doe = await fetch("/users/3")
const mattiaPhoto = await fetch("/users/2/photos/42")
🤔 "We cannot encode any possible string"
EVENTUALLY WE'LL NEED TO...
CHANGE SIGNATURE
PARAMETRIZED URLs
const john = await fetch("/users/{id}")(1)
// => User
const mattia = await fetch("/users/{id}")(2)
// => User
const mattiaPhoto = await fetch("/users/{id}/photos/{photo_id}")(2, 42)
// => Photo
PARAMETRIZED URLs
type RoutesMap = {
"/users":
{ args: [], response: User[] },
"/users/{id}":
{ args: [number], response: User },
"/users/{id}/photos/{photo_id}":
{ args: [number, number], response: Photo}
}
/* ... */
type GetResponseType<U extends URL> =
RoutesMap[U]["response"]
type GetRouteArgs<U extends URL> =
RoutesMap[U]["args"]
ANOTHER USE CASE...
TYPED FSM
A TRIBUTE TO K🎹
TYPED FINITE STATE MACHINE
HOME
WORK
BED
train
train
sleep
wakeup
TYPED FINITE STATE MACHINE
type DailyMachine = {
work: {
train: "home"
},
home: {
train: "work",
sleep: "bed"
},
bed: {
wakeup: "home"
}
}
TYPED FINITE STATE MACHINE
type DailyMachine = {
work: { /* ... */ },
home: { /* ... */ },
bed: { /* ... */ }
}
type MachineStates = keyof DailyMachine
// => work | home | bed
TYPED FINITE STATE MACHINE
type DailyMachine = { /* ... */
home: {
train: "work",
sleep: "bed"
}, /* ... */
}
type GetAvailableEvents<
S extends MachineStates
> = keyof DailyMachine[S]
type Test1 = GetAvailableEvents<"home">
// => sleep | train
TYPED FINITE STATE MACHINE
type DailyMachine = { /* ... */
home: {
train: "work",
sleep: "bed"
}, /* ... */
}
type GetNextState<
C extends MachineStates,
E extends GetAvailableEvents<C>
> = DailyMachine[C][E]
type Test1 = GetNextState<"home", "sleep">
// => bed
TYPED FINITE STATE MACHINE
declare function transition<
S extends MachineStates,
E extends GetAvailableEvents<S>
>(
state: S,
event: E
): GetNextState<S, E>;
const newState1 = transition("bed", "wakeup")
// => home
const newState2 = transition("home", "train")
// => work
const newState3 = transition("work", "sleep")
// => CompileError
AND REMEMBER...
0 JAVASCRIPT EMITTED
JUST TYPES! :D
🤯
LET'S HAVE SOME...
FUN!
HOW FAR CAN TYPE-LEVEL LOGIC GO?
WHAT ABOUT...
TYPE-LEVEL BOOLEAN LOGIC?
AT THE VERY BEGINNING... BOOLEAN LOGIC
// typelevel-booleans.ts
type TLTrue = "T"
type TLFalse = "F"
type TLBoolean = TLTrue | TLFalse
const a: TLTrue = "T" // => OK
const a: TLTrue = true // => ERROR
AT THE VERY BEGINNING... BOOLEAN LOGIC
type TLIf<A extends TLBoolean, IfTrue, IfFalse> = {
"T": IfTrue,
"F": IfFalse
}[A]
type Test1 = TLIf<TLTrue, 1, 2> // => 1
type Test2 = TLIf<TLFalse, 1, 2> // => 2
AT THE VERY BEGINNING... BOOLEAN LOGIC
// my first program
type TLSampleFunction<
AtAlicante extends TLBoolean
> = TLIf<
AtAlicante,
"Hello React Alicante!",
"I'm not done with the slides yet."
>
type TestA = TLSampleFunction<TLTrue>
// => Hello React Alicante!
type TestB = TLSampleFunction<TLFalse>
// => I'm not done with the slides yet.
AT THE VERY BEGINNING... BOOLEAN LOGIC
type TLAnd<A extends TLBoolean, B extends TLBoolean> = {
"F": TLFalse,
"T": B
}[A]
type Test1 = TLAnd<"T", "F"> // => F
type Test2 = TLAnd<"T", "T"> // => T
type Test3 = TLAnd<"F", "T"> // => F
type Test4 = TLAnd<"F", "F"> // => F
AT THE VERY BEGINNING... BOOLEAN LOGIC
type TLOr<A extends TLBoolean, B extends TLBoolean> = {
"T": TLTrue,
"F": B
}[A]
type Test5 = TLOr<"T", "F"> // => T
type Test6 = TLOr<"T", "T"> // => T
type Test7 = TLOr<"F", "T"> // => T
type Test8 = TLOr<"F", "F"> // => F
WHAT ABOUT...
TYPE-LEVEL NATURAL NUMBERS?
(ANY INTEGER POSITIVE NUMBER)
NATURAL NUMBERS
type TLZero = {
isZero: TLTrue,
prev: never
}
type TLOne = {
isZero: TLFalse,
prev: TLZero
}
type TLTwo = {
isZero: TLFalse,
prev: TLOne
}
type TLNext<N extends TLNat> = {
isZero: TLFalse,
prev: N
}
type TLOne = TLNext<TLZero>
type TLTwo = TLNext<TLOne>
type TLThree =
TLNext<TLNext<TLNext<Zero>>>
NATURAL NUMBERS
// some utilities
type IsZero<N extends TLNat> = N["isZero"]
type Test1 = IsZero<TLZero> // => "T"
type Test2 = IsZero<TLOne> // => F
NATURAL NUMBERS
// some utilities
type TLPrev<N extends TLNat> = N["prev"]
type Test1 = TLPrev<TLTwo> // => TLOne
type Test2 = TLPrev<TLZero> // => never
NATURAL NUMBERS
type SeemsSuspicious<N extends TLNat> = {
"F": { NaNa: SeemsSuspicious<TLPrev<N>>},
"T": "Batman!"
}[IsZero<N>]
type GuessWhat = SeemsSuspicious<TLEight>
NATURAL NUMBERS
type GuessWhat = {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: {
NaNa: "Batman!";
};
};
};
};
};
};
};
}
TYPE-LEVEL IS...
FUN
TYPE-LEVEL IS...
JUST AN HACK
TYPE-LEVEL IS...
DANGEROUS
CONDITIONAL TYPES
MAPPED TYPES
PHANTOM FIELDS
TYPEOF OPERATOR
TYPE LEVEL HETEROGENEOUS LISTS
EVERYONE...
THANKS FOR YOUR TIME!
@MATTIAMANZATI
TypeLevel programming fun
By mattiamanzati
TypeLevel programming fun
- 2,013