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

\text{type T = number;} \Leftrightarrow \text{let }T =\mathbb{R}
\text{let a: T;} \Leftrightarrow a \in T
\text{type T = 2;} \Leftrightarrow \text{let }T =\{ 2 \}
\text{type T = 2 | 3;} \Leftrightarrow \text{let }T =\{2\}\cup \{3\}=\{2,3\}
\text{type T = 2 \& 3 = never} \Leftrightarrow \text{let }T =\{2\}\cap \{3\}=\{\}
\text{type A extends B} \Leftrightarrow A\subseteq B
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

  1. Két bit összeadása
    • Sum (összeg, S): XOR
    • Carry (túlcsordulás, C): AND
  2. S és C kiszámolása bitenként az eredeti két szám minden bit párjára
  3. 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