Into the Unknown

@phenomnominal 2020

Into the Unknown

Hi Everyone,

I'm Craig !

  • twitter/phenomnominal
  • github/phenomnomnominal

@phenomnominal 2020

Hi Everyone,

I'm Craig !

I'm going to tell you a story!

I'm going to tell you a story!

Chapter One

Chapter One

A very helpful reindeer

A very helpful reindeer

It was nearing the end of winter in the Kingdom of Arendelle...

@phenomnominal 2020

It was nearing the end of winter in the Kingdom of Arendelle...

Sven the reindeer was feeling very productive!

@phenomnominal 2020

Sven the reindeer was feeling very productive!

He had been helping his best friend Kristoff, the Royal Ice Master!

@phenomnominal 2020

He had been helping his best friend Kristoff, the Royal Ice Master!

@phenomnominal 2020

Kristoff and Sven had been friends since they were very little...

Kristoff and Sven had been friends since they were very little...

@phenomnominal 2020

And it was their job to make sure that Arendelle had enough ice!

And it was their job to make sure that Arendelle had enough ice!

@phenomnominal 2020

Sven was always looking for ways to work smarter...

Sven was always looking for ways to work smarter...

@phenomnominal 2020

And he could always count on help from Olaf the Snowman!

And he could always count on help from Olaf the Snowman!

Less work means more time for adventures!

@phenomnominal 2020

Less work means more time for adventures!

sven and Olaf LOVE adventures...

@phenomnominal 2020

sven and Olaf LOVE adventures...

Chapter Two

Chapter Two

Sven's most magical idea

Sven's most magical idea

@phenomnominal 2020

Sven and Olaf had two other very special friends in the kingdom

Sven and Olaf had two other very special friends in the kingdom

@phenomnominal 2020

Princess Anna!

Princess Anna!

@phenomnominal 2020

And Queen Elsa!

And Queen Elsa!

@phenomnominal 2020

Anna was the brave, tenacious princess of Arendelle.

Anna was the brave, tenacious princess of Arendelle.

@phenomnominal 2020

and Queen Elsa was her big sister!

and Queen Elsa was her big sister!

Queen Elsa had amazing powers!

Queen Elsa had amazing powers!

@phenomnominal 2020

She could sense the water molecules around her,

@phenomnominal 2020

She could sense the water molecules around her,

Transform the water molecules into ice,

Transform the water molecules into ice,

@phenomnominal 2020

And control that ice however she liked!

And control that ice however she liked!

@phenomnominal 2020

Sven would never forget the Day that Elsa

explained her

magic...

@phenomnominal 2020

Sven would never forget the Day that Elsa

explained her

magic...

"I'm going to let you in on my little secret"

@phenomnominal 2020

"You may have heard that any sufficiently advanced technology is indistinguishable from magic,"

"but actually, it's the other way around..."

@phenomnominal 2020

"Any sufficiently incredible magic, is actually technology!"

@phenomnominal 2020

@phenomnominal 2020

"And the technology that makes my snow magic work is called..."

"TypeScript"

@phenomnominal 2020

Sven already Knew TypeScript from his work running the website for the local sauna, and he couldn't wait to learn more!

Sven already Knew TypeScript from his work running the website for the local sauna, and he couldn't wait to learn more!

@phenomnominal 2020

Sven and Olaf wanted to use Elsa's magic to make Kristoff's life easier!

Sven and Olaf wanted to use Elsa's magic to make Kristoff's life easier!

@phenomnominal 2020

and they had figured out the perfect plan!

and they had figured out the perfect plan!

@phenomnominal 2020

They were so excited to share it with Anna and Elsa!

They were so excited to share it with Anna and Elsa!

@phenomnominal 2020

*Snort*

@phenomnominal 2020

"That's a great idea Sven!"

"You can take Elsa's TypeScript ice magic, and create a library that anyone can use!"

"This is going to save so much time!"

Anna was thrilled!

Anna was thrilled!

@phenomnominal 2020

"Let's do this!"

"Anna and I will help you learn about TypeScript!"

Elsa couldn't wait to help!

Elsa couldn't wait to help!

Chapter Three

Chapter Three

You can't freeze everything

You can't freeze Everything

@phenomnominal 2020

Sven and Olaf got straight to work on their new library!

Sven and Olaf got straight to work on their new library!

@phenomnominal 2020

import { freeze, thaw } from '@queen-elsa/frozen';

const thing = {};

const frozen = freeze(thing);

const thawed = thaw(frozen);

They designed their ideal API...

They designed their ideal API...

@phenomnominal 2020

export function freeze (toFreeze: any): any {
  return doElsaMagic(toFreeze);
}

// TODO (Sven): Ask Elsa to write later...
function doElsaMagic (toFreeze) {
  return toFreeze;
}

They started building the functionality...

They started building the functionality...

@phenomnominal 2020

"This is going GREAT! We're amazing!"

"Anna and Elsa, what do you think?!"

@phenomnominal 2020

"Do you want to make it even better?!"

"This is really good! Great work!"

"Let's talk about Developer Experience..."

@phenomnominal 2020

import { freeze } from '@queen-elsa/frozen';
import { MagicFire } from '@bruni/fire'; 

const fire = new MagicFire();
const frozenFire = freeze(fire);

"What would happen if a user imported some Magic Fire...

and tried to freeze it?"

@phenomnominal 2020

"If they were to try to freeze MagicFire, there would be serious consequences, but our library allows it!"

"It's our job as library authors to save our users from these kinds of traps!"

"You and I know that you can't freeze MagicFire, but our users may not know that!"

@phenomnominal 2020

"Right now, we're using the any type:"

"But that's okay! It's not easy to see how best to type this code!"

export function freeze (toFreeze: any): any {
  return doElsaMagic(toFreeze);
}

@phenomnominal 2020

export function freeze (toFreeze: any): any {
  return doElsaMagic(toFreeze);
}

"We want to be able to freeze almost any input..."

"Right now the code says you can freeze absolutely anything!

@phenomnominal 2020

*grunt*

@phenomnominal 2020

"That's right Sven, we need to find some way to limit the kind of objects that can be frozen, so that no one can freeze something that is unfreezable!"

@phenomnominal 2020

export function freeze (toFreeze: any): any {
  if (toFreeze.unfreezable) {
    throw new FrozenError(`Can't freeze that! ❄️`);
  }
  return doElsaMagic(toFreeze);
}

"One thing we could do is catch the problem at run-time:"

"But we can do better!"

@phenomnominal 2020

"It would be better for the library user to know about the issue before they run the code..."

"That's where TypeScript can help! Anna, why don't you explain?"

@phenomnominal 2020

"TypeScript has lots of different types!"

"There's all the primitive types, like boolean and string,"

"There's all the built-in types, like Array and Date,"

"And then there's all the custom user-defined types!"

@phenomnominal 2020

"These types all form the TypeScript Type System!"

"One way to think about it is like a Venn diagram!"

@phenomnominal 2020

boolean

number

Array

string

Date

any

@phenomnominal 2020

"The any type is what is called a top type of the type system."

"It is a type for which all values are valid."

"It is a super-type of all other types!"

@phenomnominal 2020

"Using the any type tells TypeScript that any value can be assigned to the variable."

"When TypeScript sees an any, it basically stops type-checking from that point on!"

@phenomnominal 2020

"The any type makes our code very flexible, but it can be dangerous!"

export function freeze (toFreeze: any): any {
  // This may break at run-time,
  // but not at compile-time
  if (toFreeze.unfreezable) {
    // throw ...
  }
  return doElsaMagic(toFreeze);
}

"If the input is undefined, our code will break, but the compiler doesn't complain!"

@phenomnominal 2020

"Is that possible?!"

"So we want our library to be flexible, but without the danger?!"

@phenomnominal 2020

"Yes! TypeScript gives us another tool for this!"

"Let's look at the Venn diagram again..."

"We can use the unknown type!"

@phenomnominal 2020

boolean

number

Array

string

Date

any

unknown

@phenomnominal 2020

"The unknown type is also a top type of the type system. It is also a super-type of all other types! All values are also valid!"

"But it behaves a little bit differently...

@phenomnominal 2020

"And we get a compile-time error! We can't use the unfreezable property, because the input might not exist!"

export function freeze (toFreeze: unknown): unknown {
  // This may break at run-time,
  // so now we get an error at compile-time!
  if (toFreeze.unfreezable) {
    // throw ...
  }
  return doElsaMagic(toFreeze);
}

"We can replace the any with unknown..."

@phenomnominal 2020

"So, we need to fix our compile-time error! We need a way for the compiler to decipher the unknown value."

"One way to do this is to use a type guard!"

@phenomnominal 2020

"A type guard is a special kind of function that gives TypeScript hints about how to narrow a type."

"It tells TypeScript how to go from a broad type - like any or unknown - to a specific type!"

@phenomnominal 2020

function isUnfreezable (thing: unknown): thing is Unfreezable {
  return thing && (thing as Unfreezable).unfreezeable;
}

type Unfreezable = {
  unfreezable: true
}

export function freeze (toFreeze: unknown): unknown {
  if (!isUnfreezable(toFreeze)) {
    // toFreeze is freezable!
  }
  // throw ...
}

"A type guard is a run-time check that maps to a type:"

Special is keyword

@phenomnominal 2020

function isUnfreezable (thing: unknown): thing is Unfreezable {
  return thing && (thing as Unfreezable).unfreezeable;
}

type Unfreezable = {
  unfreezable: true
}

export function freeze (toFreeze: unknown): unknown {
  if (!isUnfreezable(toFreeze)) {
    // toFreeze is freezable!
  }
  // throw ...
}

"Right now, the implementation is a little weird..."

Weird double negative...

Confusing control flow...

@phenomnominal 2020

"It would be better if we could talk about something being freezable!"

"TypeScript has another handy type to help. Let's go back to our Venn diagram..."

@phenomnominal 2020

boolean

number

Array

string

Date

unknown

any

never

"The never type is the bottom type of the

type system. It

is a type which

has no valid

values!"

@phenomnominal 2020

type Freezable = {
  unfreezable: never
}

"We can use the never type to describe what a freezable object is:"

"An object with a value for to the unfreezable property is never assignable to Freezable!"

@phenomnominal 2020

"This means we can invert our type guard:"

// Before:
function isUnfreezable (thing: unknown): thing is Unfreezable {
  return thing && (thing as Freezable).unfreezeable;
}

// After:
function isFreezable (thing: unknown): thing is Freezable {
  return thing && !(thing as Freezable).unfreezeable;
}

export function freeze (toFreeze: unknown): unknown {
  if (isFreezable(toFreeze)) {
    // toFreeze is freezable!
  }
  // throw ...
}

No more double negative!

@phenomnominal 2020

"Another way to use never is for the return type of a function that never returns:"

function throwError (error: string): never {
  throw new FrozenError(error);
}

export function freeze (toFreeze: unknown): unknown {
  if (isFreezable(toFreeze)) {
    // toFreeze is freezable!
  }
  throwError(`Can't freeze that! ❄️`);
}

@phenomnominal 2020

"Assertion guards combine the ideas from       type guards and never:"

function assertFreezable (thing: unknown): asserts thing is Freezable {
  if (!thing || (thing as Freezable).unfreezable) {
    throw new FrozenError(`Can't freeze that! ❄️`);    
  }
}

export function freeze (toFreeze: unknown): unknown {
  assertFreezable(toFreeze);

  // toFreeze is freezable!
  return doElsaMagic(toFreeze);
}

Special asserts keyword

More sensible flow!

@phenomnominal 2020

"That's great! But we can still go further!"

export function freeze (toFreeze: unknown): unknown {
  assertFreezable(toFreeze);

  return doElsaMagic(toFreeze);
}

freeze({ unfreezable: true }); // run-time error!

"Our input is still typed as unknown..."

@phenomnominal 2020

"We can restrict our input to the Freezable type:"

export function freeze (toFreeze: Freezable): unknown {
  assertFreezable(toFreeze);

  return doElsaMagic(toFreeze);
}

freeze({ unfreezable: true }); // compile-time error!

 "Now we have run-time and compile-time safety checks!" 

Chapter Four

Chapter Four

To be frozen

To be frozen

@phenomnominal 2020

Sven and Olaf were delighted!

Sven and Olaf were delighted!

@phenomnominal 2020

"Next, we need to talk about what is involved with freezing something!"

"I use a very obscure dialect of TypeScript, that looks something like this..."

@phenomnominal 2020

([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(![]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]

"You'll need to trust me that it works!"

@phenomnominal 2020

function doElsaMagic (toFreeze: Freezable): unknown {
  const result = ([]+[])[(![]+[])[+[]...

  (toFreeze as any).frozen = result;
  return toFreeze as unknown;
}

"We can simplify it down to understand what's going on:"

@phenomnominal 2020

function doElsaMagic(toFreeze: Freezable): unknown {
  const result = ([]+[])[(![]+[])[+[]...

  (toFreeze as any).frozen = result;
  return toFreeze as unknown;
}

"But now we have some messy types to fix!"

Oh no!

@phenomnominal 2020

"We already have a Freezable type, but we need to replace the unknowns and any..."

function doElsaMagic(toFreeze: Freezable): unknown {
  const result = ([]+[])[(![]+[])[+[]...

  (toFreeze as any).frozen = result;
  return toFreeze as unknown;
}

"We need a Frozen type!"

@phenomnominal 2020

type BasicFrozen = {
  frozen: true;
}

"But it doesn't really express what we mean..."

"The most basic version could look like this:"

@phenomnominal 2020

"We want to combine the type of the Freezable thing, with the BasicFrozen type..."

Freezable

BasicFrozen

Freezable and BasicFrozen

@phenomnominal 2020

"We can use TypeScript to express this, by using generic types and intersection types..."

type Frozen<T> = T & {
  frozen: true;
}

T is a convention, but it could be named anything!

& is the syntax for an intersection type

@phenomnominal 2020

type Frozen<BaseType> = BaseType;

function Frozen (baseType) {
  return baseType;
}

type Frozen<BaseType> = BaseType & {
  frozen: true;
}

function Frozen (baseType) {
  return {
    ...baseType,
    frozen: true
  };
}

"It can be useful to think about complex types in equivalent JavaScript syntax:"

@phenomnominal 2020

type Frozen<T> = T & {
  frozen: true;
}

function freeze<T>(toFreeze: T): Frozen<T> {
  assertFreezable(toFreeze);
  (toFreeze as Frozen<T>).frozen = ([]+[])[(![]+[])[+[] ...;
  return toFreeze as Frozen<T>;
}

"And now we can almost correctly express our function:"

"Do you see what is missing?"

@phenomnominal 2020

"It doesn't say that the type of the input has to be Freezable?"

@phenomnominal 2020

"Exactly! For that we need to express one more type relationship:"

Freezable

Super-type of Freezable

@phenomnominal 2020

"For that we can use the extends keyword:"

type Frozen<T extends Freezable> = T & {
  frozen: true;
}

function Frozen (baseType: Freezable) {
  return {
    ...baseType,
    frozen: true
  };
}

Uses the extends keyword just like with classes!

@phenomnominal 2020

"We can even set a default type for the type parameter:"

type Frozen<T extends Freezable = Freezable> = T & {
  frozen: true;
}

function Frozen (baseType: Freezable = {}) {
  return {
    ...baseType,
    frozen: true
  };
}

@phenomnominal 2020

"That gives us everything we need for the freeze function!"

@phenomnominal 2020

type Freezable = {
  unfreezable: never
}



type Frozen<T extends Freezable> = T & {
  frozen: true;
}



function assertFreezable (thing: unknown): asserts thing is Freezable {
  if (!thing || (thing as Freezable).unfreezable) {
    throw new FrozenError(`Can't freeze that! ❄️`);    
  }
}



function freeze <T extends Freezable> (toFreeze: T): Frozen<T> {
  assertFreezable(toFreeze);
  
  (toFreeze as Frozen<T>).frozen = ([]+[])[(![]+[])[+[] ...;
  return toFreeze as Frozen<T>;
}

Freezable type! An object with a unfreezable property is never assignable to Freezable

Frozen type! A Frozen object is the intersection of a Freezable object and an object with the frozen property set to true

Assert Freezable function! An Assertion Guard that takes an unknown input and throws an error if the object is not Freezable

Freeze function! A function that takes a Freezable input, asserts that it is Freezable, freezes the object, and returns a Frozen type

@phenomnominal 2020

"The library is finished!?"

"Come on Sven, let's go show Kristoff right away!"

Olaf was very excited!

Olaf was very excited!

@phenomnominal 2020

And before Anna and Elsa could even try to stop them, Sven and Olaf were out the door and on their way...

And before Anna and Elsa could even try to stop them, Sven and Olaf were out the door and on their way...

Chapter Five

Chapter Five

2 fast 2 frozen

2 fast 2 frozen

@phenomnominal 2020

Olaf and Sven rushed to show Kristoff their library!

Olaf and Sven rushed to show Kristoff their library!

@phenomnominal 2020

*snort*

"Hi Kristoff! We made you a library to help make your job easier!"

@phenomnominal 2020

import { freeze } from '@queen-elsa/frozen';

const thing = {};
const frozenThing = freeze(thing);

const unfreezableThing = { unfreezable: true };
freeze(unfreezableThing); // compile-time error!

"You can use it to freeze anything you like, just like Elsa!"

@phenomnominal 2020

"You made this for me!?"

Kristoff was blown away!

Kristoff was blown away!

"Let's try it out on me right now!"

@phenomnominal 2020

Kristoff is quite the rockstar developer, so he wrote a little script very quickly...

Kristoff is quite the rockstar developer, so he wrote a little script very quickly...

@phenomnominal 2020

But just as he ran the script, Anna and Elsa burst in!

But just as he ran the script, Anna and Elsa burst in!

@phenomnominal 2020

"Wait! Stop!"

"We still need to write the thaw function!"

@phenomnominal 2020

But it was too late...
The library worked...

But it was too late...
The library worked...

@phenomnominal 2020

Olaf was frozen.

Olaf was frozen.

Chapter Six

Chapter Six

Do you want to thaw a snowman?

Do you want to thaw a snowman?

@phenomnominal 2020

"Oh no, what have I done!? I've frozen Olaf!"

Kristoff was devastated

Kristoff was devastated

@phenomnominal 2020

"We need to move fast!"

"Or Olaf could be frozen forever..."

@phenomnominal 2020

function thaw (toThaw) {
  assertFrozen(toThaw);

  delete toThaw.frozen;
  return toThaw;
}

function assertFrozen(thing) {
  if (!thing || !thing.frozen) {
    throw new FrozenError(`Can't thaw that! ❄️`);
  }
}

"Quickly, let's start with the JavaScript:"

@phenomnominal 2020

"Can't we just add any everywhere and fix it!?"

"It's too risky, we don't want something going wrong!"

@phenomnominal 2020

"Sven, why don't you add types to the      assertion guard first!"

*gulp*

@phenomnominal 2020

function assertFrozen(thing: unknown): asserts thing is Frozen {
  if (!thing || !(thing as Frozen).frozen) {
    throw new FrozenError(`Can't thaw that! ❄️`);
  }
}

Add the unknown type to the input

Cast the input to Frozen to do the check

Add the asserts statement for the return type

@phenomnominal 2020

"That's perfect!"

*clip clop!*

@phenomnominal 2020

"Now we need to add types to the thaw function!"

"Kristoff, I need your help!"

@phenomnominal 2020

function thaw<T extends Frozen>(toThaw: T) {
  assertFrozen(toThaw);

  delete toThaw.frozen;
  return toThaw;
}

"We can start by adding a generic type for the input that has to extend   Frozen?"

"But I have no idea how to do the return type! Help!"

@phenomnominal 2020

"We need to use TypeScript to extract the type of the original object from the Frozen type!"

"This is the perfect time to use..."

"Conditional Types!"

@phenomnominal 2020

T extends X ? Y : Z;

Condition

Pass

Fail

"Conditional types let us change type based on the state of other types!"

@phenomnominal 2020

type Foo<T> = T extends X ? Y : Z;

function fooType (T) {
  if (T === X) {
    return Y;
  } else {
    return Z;
  }
}

"A Conditional types is like a type function with an if statement!"

@phenomnominal 2020

"Conditional Types have an extra special power..."

"Type inference!"

@phenomnominal 2020

"Type inference gives us a mechanism to extract types from other types!"

"Imagine that we want to extract the type of the return value of a function..."

@phenomnominal 2020

type ReturnType<T> = 
  T extends (...args: Array<any>) => infer R 
    ? R
    : never;

function one () {
  return 1;
}

type A = ReturnType<typeof one>; // number

The infer tells TypeScript to work it out for us!

@phenomnominal 2020

type Parameters<T> =
  T extends (...args: infer P) => any
    ? P
    : never

function add (a: number, b: number) {
  return a + b;
}

type P = Parameters<typeof add>; // [number, number]

@phenomnominal 2020

type Thawed<T> = 
  T extends Frozen<infer R>
  ? R
  : never;

type Water = {};
type Ice = Frozen<Water>;
type W = Thawed<Ice>; // Water

"We can use type inference to extract the type of the generic Frozen object:"

@phenomnominal 2020

type Thawed<T> = T extends Frozen<infer R>
  ? R
  : never;

function thaw<T extends Frozen>(toThaw: T): Thawed<T> {
  assertFrozen(toThaw);

  delete toThaw.frozen;
  return toThaw as Thawed<T>;
}

"Okay, I got this! I'll update the thaw function!"

Add the Thawed conditional type

Use Thawed as the return type

And cast the result!

@phenomnominal 2020

"That looks perfect to me!"

"Let's save Olaf, before it's too late!"

Chapter Seven

Chapter Seven

That was weird

That was weird

@phenomnominal 2020

Kristoff, Sven, Anna & Elsa all worked together to quickly write a new script...

Kristoff, Sven, Anna & Elsa all worked together to quickly write a new script...

@phenomnominal 2020

Thanks to the library's excellent types, they were confident that the code was good...

Thanks to the library's excellent types, they were confident that the code was good...

@phenomnominal 2020

So they ran the script...

So they ran the script...

@phenomnominal 2020

And just like that, Olaf was back to his normal self!

And just like that, Olaf was back to his normal self!

@phenomnominal 2020

"Hi everyone, my name's Olaf!"

"What happened?"

"That was weird!"

@phenomnominal 2020

"Kristoff, I think you'd better explain!"

"Oh Olaf! We're so glad you're okay! So much has happened!"

@phenomnominal 2020

*Sniff!*

"Okay, I think we've got it... Ready Sven?"

@phenomnominal 2020

*yes, any's too flexi, unknown's your new bestie*

"unknown is better than any! Sven, don't you think that's true?"

*and never is pretty cool too!*

"Generics are  also quite useful, for describing unknown  types!"

*And don't forget unions, conditionals and inference*

"Your user's will have a great time!"

*That's right*

"Good types are worth the hype!"

@phenomnominal 2020

And just like that, everything was back to normal

And just like that, everything was back to normal

The End

The End

The End

@phenomnominal 2020

Thanks everyone!

Thanks everyone!

Into the Unknown!

By Craig Spence

Into the Unknown!

Craig Spence - ng-conf 2020 - Into The Unknown

  • 5,297