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
 
thornealaric profile image
thornealaric

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.