@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
import { freeze, thaw } from '@queen-elsa/frozen';
const thing = {};
const frozen = freeze(thing);
const thawed = thaw(frozen);
@phenomnominal 2022
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
// TODO (Sven): Ask Elsa to write later...
function doElsaMagic (toFreeze) {
return toFreeze;
}
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
import { freeze } from '@queen-elsa/frozen';
import { MagicFire } from '@bruni/fire';
const fire = new MagicFire();
const frozenFire = freeze(fire);
@phenomnominal 2022
@phenomnominal 2022
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
@phenomnominal 2022
export function freeze (toFreeze: any): any {
return doElsaMagic(toFreeze);
}
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
export function freeze (toFreeze: any): any {
if (toFreeze.unfreezable) {
throw new FrozenError(`Can't freeze that! ❄️`);
}
return doElsaMagic(toFreeze);
}
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
boolean
number
Array
string
Date
any
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
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 2022
@phenomnominal 2022
@phenomnominal 2022
boolean
number
Array
string
Date
any
unknown
@phenomnominal 2022
@phenomnominal 2022
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 2022
@phenomnominal 2022
@phenomnominal 2022
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 2022
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 2022
@phenomnominal 2022
boolean
number
Array
string
Date
unknown
any
never
@phenomnominal 2022
type Freezable = {
unfreezable: never
}
@phenomnominal 2022
// 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 2022
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 2022
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 2022
export function freeze (toFreeze: unknown): unknown {
assertFreezable(toFreeze);
return doElsaMagic(toFreeze);
}
freeze({ unfreezable: true }); // run-time error!
@phenomnominal 2022
export function freeze (toFreeze: Freezable): unknown {
assertFreezable(toFreeze);
return doElsaMagic(toFreeze);
}
freeze({ unfreezable: true }); // compile-time error!
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(![]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([![]]+[][[]])[+!+[]+[+[]]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]
@phenomnominal 2022
function doElsaMagic (toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
@phenomnominal 2022
function doElsaMagic(toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
Oh no!
@phenomnominal 2022
function doElsaMagic(toFreeze: Freezable): unknown {
const result = ([]+[])[(![]+[])[+[]...
(toFreeze as any).frozen = result;
return toFreeze as unknown;
}
@phenomnominal 2022
type BasicFrozen = {
frozen: true;
}
@phenomnominal 2022
Freezable
BasicFrozen
Freezable and BasicFrozen
@phenomnominal 2022
type Frozen<T> = T & {
frozen: true;
}
T is a convention, but it could be named anything!
& is the syntax for an intersection type
@phenomnominal 2022
type Frozen<BaseType> = BaseType;
function Frozen (baseType) {
return baseType;
}
type Frozen<BaseType> = BaseType & {
frozen: true;
}
function Frozen (baseType) {
return {
...baseType,
frozen: true
};
}
@phenomnominal 2022
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 2022
@phenomnominal 2022
Freezable
Super-type of Freezable
@phenomnominal 2022
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 2022
type Frozen<T extends Freezable = Freezable> = T & {
frozen: true;
}
function Frozen (baseType: Freezable = {}) {
return {
...baseType,
frozen: true
};
}
@phenomnominal 2022
@phenomnominal 2022
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 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
import { freeze } from '@queen-elsa/frozen';
const thing = {};
const frozenThing = freeze(thing);
const unfreezableThing = { unfreezable: true };
freeze(unfreezableThing); // compile-time error!
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
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 2022
@phenomnominal 2022
@phenomnominal 2020
@phenomnominal 2022
@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 2022
@phenomnominal 2022
@phenomnominal 2022
function thaw<T extends Frozen>(toThaw: T) {
assertFrozen(toThaw);
delete toThaw.frozen;
return toThaw;
}
@phenomnominal 2022
@phenomnominal 2022
T extends X ? Y : Z;
Condition
Pass
Fail
@phenomnominal 2022
type Foo<T> = T extends X ? Y : Z;
function fooType (T) {
if (T === X) {
return Y;
} else {
return Z;
}
}
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
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 2022
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 2022
type Thawed<T> =
T extends Frozen<infer R>
? R
: never;
type Water = {};
type Ice = Frozen<Water>;
type W = Thawed<Ice>; // Water
@phenomnominal 2022
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 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022
@phenomnominal 2022