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 (1)
Adoro como você usou recursão e composição para implementar operações matemáticas como soma, subtração e até exponenciação diretamente nos tipos. Isso mostra realmente o poder do TypeScript para lidar com lógica matemática de forma inovadora. Isso me lembrou de um projeto que um amigo meu fez recentemente. Ele queria criar uma ferramenta contador de dias apesar de ser iniciante em programação, ele usou ChatGPT para ajudar em todo o processo. Com o apoio do ChatGPT, ele construiu um site que calcula direitinho os dias entre datas e funciona super bem. Ele aprendeu muito com o processo e ficou muito mais confiante em seus conhecimentos de programação.
ChatGPT, É uma ótima forma de entender melhor os conceitos e conseguir ajuda rápida para implementar suas ideias.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.