.

Into the Unknown

@phenomnominal 2024

Into the Unknown

Hi Everyone,

I'm Craig !

  • he/him
  • twitter/phenomnominal
  • github/phenomnomnominal

Hi Everyone,

I'm Craig !

@phenomnominal 2024

@phenomnominal 2024

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...

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

@phenomnominal 2024

Sven the reindeer was feeling very productive!

Sven the reindeer was feeling very productive!

@phenomnominal 2024

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

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

@phenomnominal 2024

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

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

@phenomnominal 2024

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 2024

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

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

@phenomnominal 2024

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

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

@phenomnominal 2024

Less work means more time for adventures!

Less work means more time for adventures!

@phenomnominal 2024

sven and Olaf LOVE adventures...

sven and Olaf LOVE adventures...

@phenomnominal 2024

Chapter Two

Chapter Two

Sven's most magical idea

Sven's most magical idea

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 2024

Princess Anna!

Princess Anna!

@phenomnominal 2024

And Queen Elsa!

And Queen Elsa!

@phenomnominal 2024

Anna was the brave, tenacious princess of Arendelle.

Anna was the brave, tenacious princess of Arendelle.

@phenomnominal 2024

and Queen Elsa was her big sister!

and Queen Elsa was her big sister!

@phenomnominal 2024

Queen Elsa had amazing powers!

Queen Elsa had amazing powers!

@phenomnominal 2024

She could sense the water molecules around her,

She could sense the water molecules around her,

@phenomnominal 2024

Transform the water molecules into ice,

Transform the water molecules into ice,

@phenomnominal 2024

And control that ice however she liked!

And control that ice however she liked!

@phenomnominal 2024

Sven would never forget the Day that Elsa

explained her

magic...

Sven would never forget the Day that Elsa

explained her

magic...

@phenomnominal 2024

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

@phenomnominal 2024

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

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

@phenomnominal 2024

"Any sufficiently incredible magic, is actually technology!"

@phenomnominal 2024

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

"TypeScript"

@phenomnominal 2024

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 2024

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 2024

and they had figured out the perfect plan!

and they had figured out the perfect plan!

@phenomnominal 2024

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

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

@phenomnominal 2024

*Snort*

@phenomnominal 2024

"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 2024

"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!

@phenomnominal 2024

Chapter Three

Chapter Three

You can't freeze everything

You can't freeze Everything

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

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

@phenomnominal 2024

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 2024

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 2024

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

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

@phenomnominal 2024

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

"This is really good! Great work!"

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

@phenomnominal 2024

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 2024

"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 2024

"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 2024

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 2024

*grunt*

@phenomnominal 2024

"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 2024

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 2024

"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 2024

"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 2024

"These types all form the TypeScript Type System!"

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

@phenomnominal 2024

*hrrmpf*

@phenomnominal 2024

"Not a Sven diagram, Sven, a Venn Diagram!"

"Let's check it out!"

@phenomnominal 2024

boolean

number

Array

string

Date

any

@phenomnominal 2024

"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 2024

"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 2024

"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 2024

"Is that possible?!"

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

@phenomnominal 2024

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

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

"We can use the unknown type!"

@phenomnominal 2024

boolean

number

Array

string

Date

any

unknown

@phenomnominal 2024

"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 2024

"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 2024

"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 2024

"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 2024

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 2024

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 2024

"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 2024

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 2024

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 2024

"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 2024

"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 2024

"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 2024

"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 2024

"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!" 

@phenomnominal 2024

Chapter Four

Chapter Four

To be frozen

To be frozen

Sven and Olaf were delighted!

Sven and Olaf were delighted!

@phenomnominal 2024

"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 2024

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

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

@phenomnominal 2024

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 2024

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 2024

"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 2024

type BasicFrozen = {
  frozen: true;
}

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

"The most basic version could look like this:"

@phenomnominal 2024

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

Freezable

BasicFrozen

Freezable and BasicFrozen

@phenomnominal 2024

"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 2024

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 2024

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 2024

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

@phenomnominal 2024

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

Freezable

Super-type of Freezable

@phenomnominal 2024

"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 2024

"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 2024

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

@phenomnominal 2024

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 2024

"The library is finished!?"

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

Olaf was very excited!

Olaf was very excited!

@phenomnominal 2024

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...

@phenomnominal 2024

Chapter Five

Chapter Five

2 fast 2 frozen

2 fast 2 frozen

Olaf and Sven rushed to show Kristoff their library!

Olaf and Sven rushed to show Kristoff their library!

@phenomnominal 2024

*snort*

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

@phenomnominal 2024

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 2024

"You made this for me!?"

Kristoff was blown away!

Kristoff was blown away!

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

@phenomnominal 2024

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 2024

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 2024

"Wait! Stop!"

"We still need to write the thaw function!"

@phenomnominal 2024

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

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

@phenomnominal 2024

Olaf was frozen.

Olaf was frozen.

@phenomnominal 2024

Chapter Six

Chapter Six

Do you want to thaw a snowman?

Do you want to thaw a snowman?

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

Kristoff was devastated

Kristoff was devastated

@phenomnominal 2024

"We need to move fast!"

"Or Olaf could be frozen forever..."

@phenomnominal 2024

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 2024

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

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

@phenomnominal 2024

@phenomnominal 2020

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

*gulp*

@phenomnominal 2024

@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 2024

"That's perfect!"

*clip clop!*

@phenomnominal 2024

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

"Kristoff, I need your help!"

@phenomnominal 2024

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 2024

"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 2024

T extends X ? Y : Z;

Condition

Pass

Fail

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

@phenomnominal 2024

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 2024

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

"Type inference!"

@phenomnominal 2024

"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 2024

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 2024

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 2024

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 2024

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 2024

"That looks perfect to me!"

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

@phenomnominal 2024

Chapter Seven

Chapter Seven

That was weird

That was weird

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 2024

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 2024

So they ran the script...

So they ran the script...

@phenomnominal 2024

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

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

@phenomnominal 2024

"Hi everyone, my name's Olaf!"

"What happened?"

"That was weird!"

@phenomnominal 2024

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

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

@phenomnominal 2024

Prefer unknown over any when you   don't know what type something is!  

*

*

*The never type can be used to describe types that cannot happen!*

We can use Type Guards and Assertion Guards to narrow types based on run-time behaviour

@phenomnominal 2024

We can use Generics to help describe unknown types. Sub-types,    union type and intersection types   are also really useful!

*Conditional types and type inference can also be really powerful*

It's worth putting in the effort with your types so that people who consume your code have a good experience!

@phenomnominal 2024

"Neat!"

@phenomnominal 2024

The End

The End

The End

Thanks everyone!

Thanks everyone!

Slides

Code

@phenomnominal 2024

Into the Unknown! - CityJS Athens

By Craig Spence

Into the Unknown! - CityJS Athens

Craig Spence - CityJS Athens - Into The Unknown

  • 595