DEV Community

Cover image for Fazendo sua calculadora em Typelevel do TypeScript
Gabriel Grubba
Gabriel Grubba

Posted on • Originally published at blog-grubba.vercel.app

Fazendo sua calculadora em Typelevel do TypeScript

Abaco

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
Enter fullscreen mode Exit fullscreen mode

É 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;
Enter fullscreen mode Exit fullscreen mode

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>>;
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
sumaeiui profile image
Sumae

One of the company's notable strengths brushless angle grinder lies in their aerodynamics expert team, which integrates fluid mechanics seamlessly into their product offerings.