Programação de tipos - #SemanaTS dia 5
- O que você sabia e não sabia sobre TS - Semana TS dia 1
- Tipos externos e pacotes de declarações - #Semana TS dia 2
- Boas e más práticas com TypeScript - #SemanaTS dia 3
- Técnicas avançadas com TypeScript - #SemanaTS dia 4
- Programação de tipos - #SemanaTS dia 5 (você está aqui)
Parte 5 de 5 da série Semana TS
Estamos chegando ao fim da nossa semana de conteúdos sobre TypeScript, e a maioria dos conteúdos que vimos aqui até agora é relacionado a como vamos unir os nossos tipos com o nosso código, ou seja, como podemos passar mais segurança no momento de rodar nossa aplicação em produção.
Mas e se a gente usasse o sistema de tipos do TS sozinho?
Existem diversos projetos onde temos um uso extremo do TS para poder criar de tudo, teve o RPG que eu mencionei no primeiro dia, mas também temos alguém que implementou o TypeScript no TypeScript ou até mesmo uma emulação de uma máquina virtual de 4-bits usando somente tipos. Isso é o que a gente chama de Programação de tipos ou, como eu já vi bastante na Internet, Type-Level Programming.
Esse foi um conceito que eu vi primeiro em um excelente conteúdo do Gabriel Vergnaud que significa basicamente usar os tipos do TS para programar ao invés de usar uma linguagem de programação tradicional. Como eu falei, o TS é Turing-Complete, a gente pode escrever quaisquer tipos de programas com qualquer complexidade usando ele.
A ideia desse conceito é que temos que separar o que é Type-Level programming do chamado Value-Level Programming que é a programação que estamos mais acostumados, com if’s, for’s e etc. Todas essas estruturas estão presentes no TypeScript também, mas de forma implícita, por exemplo, podemos fazer um if dessa maneira:

Na verdade, esse é a implementação da própria keyword if, mas podemos fazer um if mais genérico com o que é chamado de conditional types, por exemplo, se quisermos que, quando determinado parâmetro for de um tipo, retornemos outro tipo em uma função:

Além da tipagem#
Mas muito além disso, podemos fazer sistemas completos usando somente esses tipos, principalmente quando eles se referem à lógica. Isso significa que podemos codar usando somente os tipos as portas lógicas como and, or, xor e not, e com elas podemos fazer outras estruturas mais complexas.

Para implementar uma porta XOR a gente precisa de mais um tipo que vai checar a igualdade entre dois tipos, já que o XOR somente retorna verdadeiro se ambos os tipos comparados são diferentes entre si:

Como estamos comparando dois valores booleanos apenas, podemos retornar apenas o lado direito da expressão. Portanto, pra criarmos um XOR, podemos combinar os demais tipos e retornar somente se os dois tipos não são iguais:

Interessante? Bom esse é um fragmento do código de uma biblioteca chamada expect-types, que é feita especificamente para fazer testes de tipos seguindo o mesmo modelo que usamos para testar códigos normais.

Como vimos antes no capítulo sobre declarações de tipo, é importante não só testar o código mas também testar seus tipos, principalmente para garantir que uma função está respondendo corretamente e também que testamos todos os casos possíveis. Este artigo mostra muito bem quais são as principais vantagens de se testar um tipo.
Esse é o poder que os tipos podem oferecer para a gente, a capacidade de fazer quaisquer outras aplicações, só precisamos ter um pouco de criatividade.
Além disso, perceba que a maioria das nossas chamadas estão encadeando funções uma dentro da outra, e é assim que a maioria das execuções com programação orientada a tipos costuma funcionar.
Indo além#
Como um exemplo, podemos tirar uma ideia deste artigo incrível do Ryan Dabler, onde podemos implementar aritmética no TypeScript. E o jeito como fazemos isso pode variar, mas é bastante criativo.
Primeiro, precisamos saber trabalhar com tipos em lista, como arrays e tuplas, essa é a forma como vamos extrair números variáveis de dentro dos nossos tipos. Para isso vamos fazer uso de uma propriedade legal do TypeScript que permite que a gente busque os valores diretos de uma propriedade de um objeto, então podemos pegar a propriedade length de um array por exemplo:

O que estamos fazendo aqui é usar a keyword infer para inferir o tipo literal de um valor, neste caso estamos buscando a propriedade length de um array, que é um número, agora podemos criar e modificar um valor literal conforme precisamos.
Depois vamos criar um tipo que vai ser recursivo, ou seja, vamos montar um loop recursivo que vai construir um array de acordo com o tamanho que passarmos:

O que vai acontecer é que, se executarmos como type valores: BuildTuple<3> vamos ter um tipo [any, any, any]. Ou seja, uma tupla com 3 elementos, esse tipo poderia ser descrito como a seguinte função:

A partir daí podemos construir tipos básicos, como a adição, que consiste em receber dois valores e devolver uma nova tupla com o tamanho das duas demais:

Essencialmente essa operação vai concatenar dois arrays e retornar o tamanho de ambos, se colocarmos em perspectiva, a função seria assim:

Como eu falei, precisamos ser criativos, com o TypeScript não consegue somar ou fazer nenhum tipo de operação direta, precisamos encontrar uma forma de adicionar essa funcionalidade. Infelizmente por conta de limitações do próprio TS não podemos trabalhar com nenhum número negativo, o que significa que nossa operação de subtração ficaria algo assim:

Basicamente estamos retornando o valor da primeira tupla, o que significa que precisamos que o primeiro número seja maior que o segundo.
No mundo real#
Esses projetos são estudos interessantes para chegar ao limite do sistema de tipos, mas isso não significa que vamos usar essas estruturas em uma aplicação real, ou pelo menos em casos raros.
Porém, os conceitos que vimos aqui são extremamente úteis para você ter no seu cinto de utilidades. Coisas como:
- Infer
- Conditional types
- Loops recursivos
- Generics
- Tuplas e arrays
São extremamente úteis para criar padrões de uso avançados com TypeScript. Se você está a fim de estudar um pouco mais sobre eles, fica por ai que logo mais vai ter novidade!
Pra você treinar#
Como este é o nosso último artigo da semana TS, eu resolvi fazer ele um pouco menor para poder deixar você aproveitar a sua Sexta!
Já que não vamos ter um exercício nessa edição, vamos corrigir o último exercício que tivemos!
Para declararmos uma funcionalidade nova em uma função global podemos usar as keywords declare que vimos no segundo dia, se declararmos uma função com o mesmo nome e uma assinatura diferente, vamos estar criando um aumento de tipo para um método global, dessa forma:

Agora que temos os nossos brands e nossas funções definidas, podemos testar nossa chamada da seguinte forma:

Ainda não acabou#
Primeiramente muito obrigado por acompanhar até aqui e espero que você tenha gostado do conteúdo! Não esquece de deixar um feedback aqui no formulário embaixo! Mas seria uma pena se acabasse por aqui não?
Então é por isso que, se você me ajudar a compartilhar e divulgar essa semana de conteúdos (me marca lá no twitter ou em outras redes sociais), eu vou escrever um sexto artigo que vai focar especialmente nas ferramentas e no ecossistema do TypeScript que vai sair na segunda feira (a gente tem que aproveitar o final de semana também!), então me ajuda lá!
Não esquece de deixar o seu feedback sobre a #SemanaTS aqui nesse formulário!