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