Padrões Funcionais com o TypeScript

John Tucker Blocked Unblock Seguir Seguindo 4 de janeiro

Explorando se você pode ter seu bolo e comê-lo também.

No início desta semana, encontrei-me em conflito entre a minha satisfação pessoal em usar o TypeScript e a insatisfação recentemente expressa por um líder respeitado na comunidade JavaScript .

O TypeScript continua a cair de cara na maioria das funções de ordem superior. Talvez eu simplesmente não saiba como usá-lo corretamente (depois de anos vivendo com ele regularmente – nesse caso, eles realmente precisam melhorar a usabilidade, a documentação ou ambos), mas eu ainda não sei como digite a operação do mapa no TypeScript, e parece estar alheio a qualquer coisa acontecendo em um transdutor. Ele não detecta erros e frequentemente reclama de erros que na realidade não são erros.

Eric Elliott – Principais estruturas e tópicos de JavaScript para aprender em 2019

Depois de ler e escrever sobre a excelente série de Eric Elliott sobre programação funcional com JavaScript, começando com Composing Software: An Introduction , pensei em revisitar alguns padrões funcionais com o TypeScript .

Functors

Um padrão interessante e mais complicado é um functor.

Um tipo de dados functor é algo que você pode mapear. É um contêiner que possui uma interface que pode ser usada para aplicar uma função aos valores dentro dela. Quando você vê um functor, você deve pensar “mapeável”. Os tipos de Functor são tipicamente representados como um objeto com um método .map () que mapeia de entradas para saídas enquanto preserva a estrutura. Na prática, “preserving structure” significa que o valor de retorno é o mesmo tipo de functor (embora os valores dentro do container possam ser de um tipo diferente).

– Eric Elliott – Functors & Categories

Enquanto JavaScript arrays com seu método map , são functors, podemos criar o nosso próprio em JavaScript (exemplo, cortesia de Eric Elliott ), por exemplo, os objetos ( a e c ) retornados pela função identity :

Edit 1/4/19 : No meu primeiro passo, eu deixei de retornar um functor da função map ; grande erro . Além disso, adicionamos algumas funções de mapeamento mais complexas, especialmente a conversão que alterna tipos (de número para string) para tornar o problema mais desafiador.

No exemplo JavaScript acima, usamos deliberadamente o functor c , tentando multiplicar uma string por 2, obtendo assim um resultado inesperado em tempo de execução; Na verdade, ele retorna a mesma string; cat .

Vamos reescrever este código no TypeScript :

note : Uma das partes complicadas do TypeScript é Generics ; assumindo que o leitor esteja familiarizado com este conceito.

Edit 04/01/19 : Obviamente, desde que eu mudei o exemplo de JavaScript , fui forçado a mudar o exemplo de TypeScript .

Converter isso de JavaScript para Datilografado significava criar uma interface mapeável e fornecer com muito cuidado os tipos para vários parâmetros e tipos de retorno.

No exemplo de TypeScript acima, novamente usamos deliberadamente o functor c , tentando multiplicar uma string por 2. Desta vez, no entanto, recebemos um erro de compilação (como mostrado no meu editor, Visual Studio Code , com uma mensagem de erro completamente compreensível .

Progressão de Objetos

Eric Elliott também descreve uma abordagem interessante para implementar a complexidade crescente:

Comece com a implementação mais simples e mova para implementações mais complexas somente conforme necessário. Quando se trata de objetos, essa progressão parece um pouco com isso:
Função pura -> Objeto> Fábrica -> Mixin Funcional -> Classe

– Eric Elliott – Mixins Funcionais

Anteriormente eu escrevi um artigo, Exemplos em JavaScript Functional Programming: Parte 3 , onde criei exemplos de cada um dos padrões, em particular:

Pensei em usar uma série de exemplos simples e consistentes para demonstrar essa orientação. Por razões de familiaridade, todos os exemplos envolvem o cálculo de uma linha:
y = (x * declive) + intercepção

– John Tucker – Exemplos em JavaScript Programação Funcional: Parte 3

Vamos recriar esses exemplos em TypeScript ; explorando quaisquer desafios que encontrarmos:

Função Pura

A maneira mais simples de fornecer código reutilizável é através de uma função pura.

Uma função pura é uma função em que o valor de retorno é determinado apenas por seus valores de entrada, sem efeitos colaterais observáveis. Algumas coisas comuns para evitar:

  • Não mude a entrada, por exemplo, Array.prototype.push () .
  • Não use variáveis (alteráveis) fora do escopo da função.

Converter isso de JavaScript para Typescript simplesmente equivalia a fornecer o tipo de número no parâmetro da função ( x ); o valor de retorno é inferido.

Neste e nos exemplos a seguir, obtemos segurança de tipo valiosa (com verificações em tempo de compilação). Por exemplo, em JavaScript , poderíamos ter usado indevidamente myPureFunction , fornecendo-lhe uma string com um resultado de tempo de execução inesperado. No TypeScript, isso resultaria em um erro em tempo de compilação (visível no editor).

Objeto

Podemos usar um literal de objeto para entregar o mesmo código reutilizável, mas adicionando a capacidade de alterar a inclinação e a interceptação. É importante notar que há apenas um objeto (um singleton).

Converter isso de JavaScript para Typescript simplesmente equivale a fornecer os tipos de parâmetro e retorno, ambos number , no método y .

Uma coisa interessante que aprendi ao escrever este artigo é que, com o TypeScript , isso é digitado como qualquer outro ; explicando por que precisamos fornecer explicitamente o tipo de retorno.

Fábrica

Podemos querer ter mais de um desses objetos; cada um com sua própria inclinação e interceptação.

Converter isso do JavaScript para o Typescript é um pouco mais complicado, já que precisamos definir a estrutura do objeto usando uma interface MyObject ; Fora disso, precisamos apenas fornecer os tipos de parâmetro e retorno para os três métodos setIntercept , setSlope e y .

Mixin Funcional

A complexidade adicional dos mixins de funções permite criar objetos que derivam funcionalidades de várias origens. Neste exemplo, temos dois objetos ( objetoA e objetoB ) que são aprimorados com a funcionalidade de myFunctionalMixin .

Este é o exemplo mais complicado para converter em TypeScript, já que precisamos usar Generics . Ao mesmo tempo, as alterações equivalem apenas à criação da interface MyMixedObject e ao fornecimento de parâmetros e tipos de retorno em várias funções.

Classe

Muito parecido com mixins funcionais, você pode usar classes para criar objetos que derivam funcionalidades de múltiplas fontes; Realmente não há nova capacidade aqui. Neste caso, criamos objetos do tipo ClassA e ClassB que, por sua vez, derivam a funcionalidade do MyClass .

Sem surpresa, converter o exemplo de classe em TypeScript era trivial; simplesmente precisava digitar as propriedades de interceptação e inclinação e fornecer modificadores de visibilidade nas propriedades e métodos da classe.

Uma observação interessante é que, com as classes, o TypeScript pode manipular a propriedade this keyword; Assim, os tipos de retorno do método podem ser inferidos corretamente.

Conclusão

No final, achei bastante simples usar o TypeScript com vários padrões funcionais; É certo que o exemplo funcional de mixin levou-me um pouco de tempo para ficar em linha reta.

Edit 1/4/19: O exemplo functor acabou por ser mais difícil do que eu pensava inicialmente.

Conclusão, não vejo como o TypeScript interfere em um texto em um estilo funcional. Parece que você pode ter seu bolo e comê-lo também.

Apreciar!