Uma das peças mais antigas da humanidade feita para fazer aritmética é o abaco e sua versão moderna é a calculadora. Nesse blogpost irei fazer uma tour por uma feita a partir o sistema de tipos do TypeScript.
Antes de começar farei um agradecimento ao @noghartt por me mostrar essa wiki sensacional e me ajudar bastante nessa construção.
Vou supor que você tenha lido o blogpost de introdução ao assunto link
O começo
Vamos partir de algumas regras, uma delas é que estamos em um sistema decimal. Sendo assim, para chegar no próximo número iremos adicionar +1 ou remover -1 e graças a sua posição saberemos se trata-se de um número em dezenas, centenas ou milhares.
Para começar vamos criar a soma de um e subtração de um:
type Reverse<A> =
`${A}` extends `${infer AH}${infer AT}`
? `${Reverse<AT>}${AH}` : A;
type Digs = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type DigsNext<I = Digs, R = {}> =
I extends [infer Head, infer Next, ...infer Tail]
? DigsNext<[Next, ...Tail], R & Record<Head, Next>>
: { [K in keyof R]: R[K] };
type DigsPrev = { [K in keyof DigsNext as DigsNext[K]]: K };
type ToNumber<
S extends string,
L extends number[] = []>
= `${L['length']}` extends S
? L['length']
: ToNumber<S, [...L, 0]>;
type GreaterThan<
T extends number,
U extends number,
C extends unknown[] = []
> =
T extends U
? false
: C['length'] extends T
? false
: C['length'] extends U
? true
: GreaterThan<T, U, [...C, 1]>;
type AddOne<A> =
A extends `${infer AH}${infer AT}`
? AH extends '9' ? `0${AddOne<AT>}` : `${DigsNext[AH]}${AT}`
: `1`
type SubOne<A> =
A extends `${infer AH}${infer AT}`
? AH extends '0' ? `9${SubOne<AT>}` : `${DigsPrev[AH]}${AT}`
: never
É só isso ?
Para a surpresa de muitos a mátematica depois da criação dessas funções base nada mais é do que a composição e recursão delas, ou seja, repetir N vezes a soma de +1 até ter uma soma 'normal' ou N vezes a subtração para obter a subtração 'normal'
type Sub<
A extends string,
B extends string,
R extends string = "0"
> =
B extends R
? A
: Sub<SubOne<A>, B, AddOne<R>>;
type Add<A, B> =
A extends `${infer AH}${infer AT}` ?
B extends `${infer BH}${infer BT}`
? BH extends '0' ? `${AH}${Add<AT, BT>}` : Add<AddOne<A>, SubOne<B>>
: A : B;
Criação da soma e da subtração como descrita acima
Recursão
Para obtermos as operações de multiplicação e divisão devemos fazer a recursão da soma e da subtração recursivamente até obtermos nosso resultado. O que pode ser observado no snippet abaixo:
type Mul<A, B, R = '0'> =
A extends '0' ? R :
B extends '0' ? R :
A extends `${infer AH}${infer AT}`
? AH extends '0' ? Mul<AT, `0${B}`, R> : Mul<SubOne<A>, B, Add<R, B>>
: R;
type Multiply<A extends string | number | bigint, B extends string | number | bigint> =
Reverse<Mul<Reverse<A>, Reverse<B>>>;
type Division<
W extends string,
D extends string,
Q extends string = '0'
> =
W extends '0'
? Q
: Division<Sub<W, D>, D, AddOne<Q>>;
Diversão
Brincando com recursão e a composição dessas funções primordiais obtemos mais algumas operações como as de potencia e de Log:
type Power<
V extends string,
P extends string,
A extends string = V> =
P extends '1'
? A
: P extends '0'
? '1'
: Power<V, SubOne<P>, Multiply<V, A>>;
// Log<10, 100> = 2
type Log<
B extends string,
L extends string,
I extends string = "0",
PA extends string = "0"> =
L extends "1"
? "0"
: L extends PA
? I
: GreaterThan<ToNumber<PA>,ToNumber<L>> extends true
? never
: Log<B, L, AddOne<I>, Power<B, AddOne<I>>>;
const $: Power<'2', '4'> = "16";
const _: Power<'2', '3'> = "8";
Resultado
Como pode ser observado nessa calculadora e partindo do zero pode-se criar a matématica em qualquer ambiente que seja póssivel criar variáveis, recursão/ iteração e controle de fluxo(if's). O final da caluladora é esse:
type OpDict<A extends string, B extends string> = {
'Div': Division<A, B>,
'Mul': Multiply<A, B>,
'Sum': Add<A, B>,
'Sub': Sub<A, B>,
'Log': Log<A, B>,
'Pow': Power<A, B>
};
type Calculator
<Operation extends keyof OpDict<string, string>,
A extends string = '0',
B extends string = '0'
> = OpDict<A, B>[Operation];
const teste: Calculator<'Mul', '2', '2'> // 4
const teste: Calculator<'Mul', '2', '8'> // 16
const teste: Calculator<'Sum', '2', '4'> // 6
const teste: Calculator<'Sub', '9', '4'> // 5
Com isso chegamos na conclusão de mais uma saga. Dessa vez envolveu um pouco menos de conceitos e sim um pouco mais de trabalho no sentido de transformar ideias abstratas em funções reais.
Quer ver rodando em sua máquina ? link para plaground do ts e gist
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.