.
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
import { freeze, thaw } from '@queen-elsa/frozen';
const thing = {};
const frozen = freeze(thing);
const thawed = thaw(frozen);
@phenomnominal 2024
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
// TODO (Sven): Ask Elsa to write later...
function doElsaMagic (toFreeze) {
return toFreeze;
}
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
import { freeze } from '@queen-elsa/frozen';
import { MagicFire } from '@bruni/fire';
const fire = new MagicFire();
const frozenFire = freeze(fire);
@phenomnominal 2024
@phenomnominal 2024
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
@phenomnominal 2024
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
export function freeze (toFreeze: any): any {
if (toFreeze.unfreezable) {
throw new FrozenError(`Can't freeze that! ❄️`);
}
return doElsaMagic(toFreeze);
}
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
boolean
number
Array
string
Date
any
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
export function freeze (toFreeze: any): any {
// This may break at run-time,
// but not at compile-time
if (toFreeze.unfreezable) {
// throw ...
}
return doElsaMagic(toFreeze);
}
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
boolean
number
Array
string
Date
any
unknown
@phenomnominal 2024
@phenomnominal 2024
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);
}
@phenomnominal 2024
@phenomnominal 2024
@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 ...
}
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 ...
}
Weird double negative...
Confusing control flow...
@phenomnominal 2024
@phenomnominal 2024
boolean
number
Array
string
Date
unknown
any
never
@phenomnominal 2024
type Freezable = {
unfreezable: never
}
@phenomnominal 2024
// 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
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
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
export function freeze (toFreeze: unknown): unknown {
assertFreezable(toFreeze);
return doElsaMagic(toFreeze);
}
freeze({ unfreezable: true }); // run-time error!
@phenomnominal 2024
export function freeze (toFreeze: Freezable): unknown {
assertFreezable(toFreeze);
return doElsaMagic(toFreeze);
}
freeze({ unfreezable: true }); // compile-time error!
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(![]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]
@phenomnominal 2024
function doElsaMagic (toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
@phenomnominal 2024
function doElsaMagic(toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
Oh no!
@phenomnominal 2024
function doElsaMagic(toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
@phenomnominal 2024
type BasicFrozen = {
frozen: true;
}
@phenomnominal 2024
Freezable
BasicFrozen
Freezable and BasicFrozen
@phenomnominal 2024
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
};
}
@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>;
}
@phenomnominal 2024
@phenomnominal 2024
Freezable
Super-type of Freezable
@phenomnominal 2024
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
type Frozen<T extends Freezable = Freezable> = T & {
frozen: true;
}
function Frozen (baseType: Freezable = {}) {
return {
...baseType,
frozen: true
};
}
@phenomnominal 2024
@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
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
import { freeze } from '@queen-elsa/frozen';
const thing = {};
const frozenThing = freeze(thing);
const unfreezableThing = { unfreezable: true };
freeze(unfreezableThing); // compile-time error!
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@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! ❄️`);
}
}
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2020
@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
@phenomnominal 2024
@phenomnominal 2024
function thaw<T extends Frozen>(toThaw: T) {
assertFrozen(toThaw);
delete toThaw.frozen;
return toThaw;
}
@phenomnominal 2024
@phenomnominal 2024
T extends X ? Y : Z;
Condition
Pass
Fail
@phenomnominal 2024
type Foo<T> = T extends X ? Y : Z;
function fooType (T) {
if (T === X) {
return Y;
} else {
return Z;
}
}
@phenomnominal 2024
@phenomnominal 2024
@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
@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>;
}
Add the Thawed conditional type
Use Thawed as the return type
And cast the result!
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
@phenomnominal 2024
Slides
Code
@phenomnominal 2024