Fazendo sua calculadora em Typelevel do TypeScript
Tags: typescript, showdev, tutorial, programming
Published: 2022-07-21 03:30:15


![Abaco](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/px7m7pg0e2d76jp2h6jg.jpeg)

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](https://twitter.com/noghartt) por me mostrar essa [wiki](https://en.wikipedia.org/wiki/Peano_axioms#) sensacional e me ajudar bastante nessa construção.

Vou supor que você tenha lido o blogpost de introdução ao assunto [link](https://dev.to/grubba/tenha-brio-com-sistemas-de-tipos-h7d)

## 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:

```ts
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'

```ts
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:

```ts
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:

```ts
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:

```ts
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](https://www.typescriptlang.org/play?#code/C4TwDgpgBAShBuEBOBnCAeAggPigXgCgooADAEgG9MBfEqCAD2AgDsATFUygSxYDNkUTAAlqPfoMwAVWkWJQA-FwpxEqDNOxiqougC4hBAqEhQAItwDmnPFADaABgA0UAIwuATC4DMLgCwuAKwuAGwuAOwuABwuAJwAusbg0BbWAHKMwOgAkvjmViguMHkU1LiExLmZrBz2vAJIUMIQAIZsLvWCGUwuAHT9nY1SLdwANonyivnpmeh23cB9-cNj8UVQAGSwEADGAPZIbOjNbS4L2NhyxAYU9gDSULxQANYQIHt8sPEGMHZ38VBqElTKkUAAFJAIEr3R4sF5vD7TFALKAtTighZ-eLfKAPIEmaBSPZpACuAFsAEbIdByADK9CYNU4KGASF4liccgAMgzmOxOCxyVSkHYAbZRZdiLZyBQuXYAOSjViWYAAC3l8VovKZUFpciUcsVyrVGrkBiJpMp1NpLjs-V6XJcDnil2B0AA4pCWswkFJVS0WDTiFJtfyoIKrUhOcQAKqh2oR4XRqAAYXjnBJLGeLD2AHcWKK8qK5OU5CHqmGY1cpnwWqM0NWDCmFUqWCr1QCK7UpNXiEpa-WIL2oE2W8aO+moFXJpMlKySUOZ-IDJ7Wj6-QH0FIXDHbfaUy5XC6ANxuoRsNgAeRYGlLxEwk5lgyEonEDSEMhI1aUIkn8ti8pTCQDiUJgF7XhoUhaPoygYpkdgiJqoGfo2pCuF+BK6iSFIQVgd5CI+b6SK+FDPtIsizi+f4OIBSgkLElC0thuGaFqBgyqCEIIAhwhIVQKGTAYN5qGeTEUkGBFdsyrLssmABCk4smybbJsUUlQEp7J5AARA42klvgcgKepMDfoYglYeJYksdgLhyS4YFXje6AwBcp5no5WB2fhD7qU+EiNCI2hkZ+ijVsZjJhv575ySRz5yQJM5KLF1G0coQWgReWDblACXQSO55HI5NkuNZzlyRcw4GA+BhyWeACyJKjF5uXrLY8o0T5qVTMUejhd1Si9cOvmRbU0XEcFAUfhRS5TL+6kdWljXNdILjAZQcm0EUuAGMt6BlbedkOVlMDeZKs0-EYmHLcA3BgKMIBYIpMltlAAA+4ZCoIH0UlYvCLLlz3KZY72fZGoO-ZY-34aoyBoOge2w+oeFFAgcMYBVlVnhY8DcCg3B7IGcgAOpA7JchmGTKlyAAilTIPtTRBkVFApMLUzlE06hON4wTgZiegxMuGYtnmMdTkYDTWOYWCebUnIABq9PJmCytyCNfK1Jpr22Ar+GqwtrjymZmCoQbo2cItw5KPKRtVVAsu5tSCulcxzlgqLN13Q96Au0IWNGAQAD0QdQFyeyWOgrjOG4DgOOUUAeGe4eR0ZavEDy6naxychVBbGkvQzUC6dpKsazq2c6Xp+GZ-n2muPplEl6htea5wYKm0uSjZPbq7esgG6BhaX1IOgHe2cPkboFyFyTvOi6zUownIMO1xhxH6D2WH4u4dkouO9SW-Fc5e9uUY+wsCyUBkAYB+j-KHjyi48p+PKCf1yE2mnhfV8APq33Le+j9n7eDfjpKIX8rrJCgJeMAFgdhZHLmGbOdl6a4BKHIeUON5QGB5vjQmLUKrJnlMtHBUAvb3UepgbyxCmJkjIZ5ahuVbKYLEmQgWTCiGYJTmQlOhCWHEHlI7Mhd9+EEGoO5TCKY6w7Cat6A4ch0CwOQN6Pmk5XjvE+LA+BWQUEF2BgI+QSCtaFzyItZMxAIpt30VpRmxtJgJ20dwBB-C7DKKQKowm8R3I-2AFAZgLIIBNhkXI4ABx0AkKak-KAD9omxNwCHKAfgCC+P8RAQJwTRiyNGPI++pDn7AJiVEMBiTXAhBSYTK+ATmCZOybkiJdC4mFJfiU0O5TUnVKCamEJOSwn3zYc-ACz9X4JNDoEIAA) para plaground do ts e [gist](https://gist.github.com/Grubba27/a9dd3928f3cd996072df4d3b3c3f3e3bhttps://gist.github.com/Grubba27/a9dd3928f3cd996072df4d3b3c3f3e3b)