Programozás a TypeScript típus rendszerében

\


WebGurus
webgurus.io

Godra Ádám
Full-Stack SWE



TypeScript röviden
TS = JS + opcionális statikus típus annotációk
tsc: TypeScript compiler
transpiler
tsc output = JS kód + hibák
lényegében egy linter

JS: let, var, for, const, if, else, while, try, catch, ...
TS: let, var, for, const, if, else, while, try, catch, type, interface, enum, extends, <T>, string, number, ...

⊃

TS = JS superset
Legyen TOS* =
TOS: type, interface, enum, extends, <T>, string, number, ...

\

*TOS = TypesOnlyScript


build time
run time
src/index.ts
dist/index.js



tsc ./src -o ./dist --noCheck

type
enum
interface
number
extends
string
“One man's trash is another man's treasure.”

TOS: Turing-teljes!

Turing-teljesség
értékadás
elágazás
iteráció
Egy programozási nyelv akkor Turing-teljes, ha van benne:

Turing-teljesség (JS)
értékadás: var, const, let
elágazás: if, else, else if
iteráció: for, while, do while

Turing-teljesség (FP)
értékadás: function
elágazás: function
iteráció: function

Turing-teljesség (TOS)
értékadás: type
elágazás: type
iteráció: type

Kitérő

type = halmaz
type Two = 2;
let two: Two = 2; // ok
let notTwo: Two = 1; // TypeScript hiba
Értékadás

type If<C, T, F> = C extends true ? T : F;
type Equals<A, B> = A extends B ? B extends A ? true : false : false;
type IsTwoAsString<T extends number> = If<Equals<T, 2>, "two", "not two">;
type Test1 = IsTwoAsString<2>; // type Test = "two"
type Test2 = IsTwoAsString<3>; // type Test = "not two"
type Test3 = IsTwoAsString<"asd">; // TypeScrript hiba
Elágazás

type AddHello<S> = S extends string ? `Hello ${S}` : never;
type AddHelloToEach<T extends any[]> = T extends [infer H, ...infer R]
? [AddHello<H>, ...AddHelloToEach<R>]
: [];
type Names = ["Mary", "Gary", "Larry", {}];
type Test = AddHelloToEach<Names>;
// type Test = ["Hello Mary", "Hello Gary", "Hello Larry", never]
Iteráció

Cél
type BinaryAdd<S1 extends string, S2 extends string> = ???;
type Test = BinaryAdd<"1001", "101">; // 9 + 5
// type Test = "1110" i.e. 14

Terv
-
Két bit összeadása
- Sum (összeg, S): XOR
- Carry (túlcsordulás, C): AND
- S és C kiszámolása bitenként az eredeti két szám minden bit párjára
- A kapott S-el és C << 1-el megismételjük a második lépést, ezáltal egy új S-et és C-t kapunk, majd így tovább amíg C != 0

1.
A 11010 +
B 1101 =
S1 10111
C1 01000
3.
S2 000111 +
C2<<1 100000 =
S3 100111
C3 000000
Példa

2.
S1 10111 +
C1<<1 10000 =
S2 00111
C2 10000
4. END
S = S3 = 100111 (39)
Terv
// bit operations
type BIT = 0 | 1;
type AND<A extends BIT, B extends BIT> = unknown;
type XOR<A extends BIT, B extends BIT> = unknown;
// bit string operations
type ShiftLeft<S extends Array<BIT> = unkown;
type BitWiseAND<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
type BitWiseXOR<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
type Reverse<T extends any[]> = unknown;
// business logic
type BinaryAddStep<S1 extends Array<BIT>, S2 extends Array<BIT>> unknown;
type BinaryAdd<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
// test
type Equals<S1 extends Array<BIT>, S2 extends Array<BIT> = unknown; // true | false
type Test1 = Equals<BinaryAdd<"0", "1">, "1">;
type Test2 = Equals<BinaryAdd<"10", "1">, "11">;
type Test3 = Equals<BinaryAdd<"11", "10">, "101">;

Bit operátorok
=> XOR
=> AND
XOR = (!A & B) | (A & !B)

A/B | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
S
A/B | 0 | 1 |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
C
Bit operátorok TOS-ben
type BIT = 0 | 1;
type NOT<B extends BIT> = B extends 1 ? 0 : 1;
type AND<B1 extends BIT, B2 extends BIT> = [B1, B2] extends [1, 1] ? 1 : 0;
type OR<B1 extends BIT, B2 extends BIT> =
AND<NOT<B1>, NOT<B2>> extends 1 ? 0 : 1;
type XOR<B1 extends BIT, B2 extends BIT> =
OR<AND<NOT<B1>, NOT<B2>>, AND<B1, B2>> extends 1 ? 0 : 1;

Terv
// bit operations: done
type BIT = 0 | 1;
type NOT<B extends BIT> = B extends 1 ? 0 : 1;
type AND<B1 extends BIT, B2 extends BIT> = [B1, B2] extends [1, 1] ? 1 : 0;
type OR<B1 extends BIT, B2 extends BIT> = AND<NOT<B1>, NOT<B2>> extends 1 ? 0 : 1;
type XOR<B1 extends BIT, B2 extends BIT> = OR<AND<NOT<B1>, NOT<B2>>, AND<B1, B2>> extends 1 ? 0 : 1;
// TODO: bit string operations
type ShiftLeft<S extends Array<BIT> = unkown;
type BitWiseAND<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
type BitWiseXOR<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
type Reverse<T extends any[]> = unknown;
// business logic
type BinaryAddStep<S1 extends Array<BIT>, S2 extends Array<BIT>> unknown;
type BinaryAdd<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
// test
type Equals<S1 extends Array<BIT>, S2 extends Array<BIT> = unknown; // true | false
type Test1 = Equals<BinaryAdd<"0", "1">, "1">;
type Test2 = Equals<BinaryAdd<"10", "1">, "11">;
type Test3 = Equals<BinaryAdd<"11", "10">, "101">;

Bitstring operátorok
ShiftLeft & Reverse
type ShiftLeft<S extends Array<BIT>> = [...S, 0];
// type ShiftLeft<[1, 0, 1]> = [1, 0, 1, 0]
type Reverse<S extends Array<BIT>> = S extends [
infer H,
...infer R extends BIT[],
]
? [...Reverse<R>, H]
: [];
// type Reverse<[1, 0, 1, 1]> = [1, 1, 0, 1]

Bitstring operátorok
Bitwise XOR
type BitwiseXOR<
S1 extends Array<BIT>,
S2 extends Array<BIT>,
Acc extends Array<BIT> = [],
> = S1 extends [infer F1 extends BIT, ...infer R1 extends BIT[]]
? S2 extends [infer F2 extends BIT, ...infer R2 extends BIT[]]
? XOR<F1, F2> extends infer R extends BIT
? BitwiseXOR<R1, R2, [...Acc, R]>
: never
: XOR<F1, 0> extends infer R extends BIT // S1.length > S2.length
? BitwiseXOR<R1, [], [...Acc, R]>
: never
: S2 extends [infer F2 extends BIT, ...infer R2 extends BIT[]]
? XOR<0, F2> extends infer R extends BIT // S2.length > S1.length
? BitwiseXOR<[], R2, [...Acc, R]>
: never
: Acc; // S1.length = S2.length

Bitstring operátorok
Bitwise AND
type BitwiseAND<
S1 extends Array<BIT>,
S2 extends Array<BIT>,
Acc extends Array<BIT> = [],
> = S1 extends [infer F1 extends BIT, ...infer R1 extends BIT[]]
? S2 extends [infer F2 extends BIT, ...infer R2 extends BIT[]]
? AND<F1, F2> extends infer R extends BIT
? BitwiseAND<R1, R2, [...Acc, R]>
: never
: AND<F1, 0> extends infer R extends BIT // S1.length > S2.length
? BitwiseAND<R1, [], [...Acc, R]>
: never
: S2 extends [infer F2 extends BIT, ...infer R2 extends BIT[]]
? AND<0, F2> extends infer R extends BIT // S2.length > S1.length
? BitwiseAND<[], R2, [...Acc, R]>
: never
: Acc; // S1.length = S2.length

Bitstringek megfordítása
type BitwiseANDWrapper<S1 extends Array<BIT>, S2 extends Array<BIT>>
= Reverse<BitwiseAND<Reverse<S1>, Reverse<S2>>>;
type BitwiseXORWrapper<S1 extends Array<BIT>, S2 extends Array<BIT>>
= Reverse<BitwiseXOR<Reverse<S1>, Reverse<S2>>>;

Terv
// bit operations: done
type BIT = 0 | 1;
type NOT<B extends BIT> = B extends 1 ? 0 : 1;
type AND<B1 extends BIT, B2 extends BIT> = [B1, B2] extends [1, 1] ? 1 : 0;
type OR<B1 extends BIT, B2 extends BIT> = AND<NOT<B1>, NOT<B2>> extends 1 ? 0 : 1;
type XOR<B1 extends BIT, B2 extends BIT> = OR<AND<NOT<B1>, NOT<B2>>, AND<B1, B2>> extends 1 ? 0 : 1;
// TODO: bit string operations
type ShiftLeft<S extends Array<BIT> = /* see above*/;
type BitWiseAND<S1 extends Array<BIT>, S2 extends Array<BIT>> = /* see above*/;
type BitWiseXOR<S1 extends Array<BIT>, S2 extends Array<BIT>> = /* see above*/;
type Reverse<T extends any[]> = /* see above*/;
// business logic
type BinaryAddStep<S1 extends Array<BIT>, S2 extends Array<BIT>> unknown;
type BinaryAdd<S1 extends Array<BIT>, S2 extends Array<BIT>> = unknown;
// test
type Equals<S1 extends Array<BIT>, S2 extends Array<BIT> = unknown; // true | false
type Test1 = Equals<BinaryAdd<"0", "1">, "1">;
type Test2 = Equals<BinaryAdd<"10", "1">, "11">;
type Test3 = Equals<BinaryAdd<"11", "10">, "101">;

Egy lépés...
// egy iteráció, kiszámoljuk az összeget és a túlcsorduló biteket
type BinaryAddStep<S1 extends Array<BIT>, S2 extends Array<BIT>> = [
BitwiseXORWrapper<S1, S2>,
ShiftLeft<BitwiseANDWrapper<S1, S2>>,
];

...ismételve
// ha C == 0, visszatérítjük az utolsó S-t,
// másként rekurzívan meghívjuk magunkat az új S-el és C-vel
type BinaryAddBitArray<S1 extends Array<BIT>, S2 extends Array<BIT>>
= BinaryAddStep<S1, S2> extends [
infer S extends BIT[],
infer C extends BIT[],
] ? IsZero<C> extends true
? S
: BinaryAddBitArray<S, C>
: never;

IsZero
// ha egy bitstring minden eleme 0 akkor true, másként false
type IsZero<S extends BIT[]> = S extends [infer F, ...infer R extends BIT[]]
? F extends 1
? false
: IsZero<R>
: true;
type TrimZeroes<S extends BIT[]> = S extends [
infer B extends BIT,
...infer R extends BIT[],
]
? B extends 0
? TrimZeroes<R>
: S
: [];
type TestTrimZeroes = TrimZeroes<[0, 0, 1, 0, 1, 1]>; // [1, 0, 1, 1]

& TrimZeroes
Majdnem kész
type StringToBits<S extends string> = S extends `${infer H}${infer R}`
? H extends "1"
? [1, ...StringToBits<R>]
: [0, ...StringToBits<R>]
: [];
type TestStringToBits = StringToBits<"10010">; // [1, 0, 0, 1, 0]
type BitsToStrings<S extends BIT[]> = S extends [
infer H,
...infer R extends BIT[],
]
? H extends 1
? `1${BitsToStrings<R>}`
: `0${BitsToStrings<R>}`
: "";
type TestBitsToString = BitsToStrings<[1, 0, 0, 1]>; // "1001"

BinaryAdd 🎉
type BinaryAdd<S1 extends string, S2 extends string> = BitsToStrings<
TrimZeroes<BinaryAddBitArray<StringToBits<S1>, StringToBits<S2>>>
>;

Linkek

TOS is Turing Complete
By Godra Adam
TOS is Turing Complete
- 42