9 Soluções Práticas em Programação Funcional para Você Saber (em TypeScript e Node)

Exemplos simples para qualquer desenvolvedor

Alison Miazaki
8 min readMar 6, 2021
Foto de Jeswin Thomas no Unsplash

Esse artigo e uma tradução livre do artigo 9 Useful Functional Programming Solutions You Can Learn (in TypeScript and Node) escrito por Ardy Gallego Dedase.

Como desenvolvedores sempre nos encontramos nas mesma situações: escrevendo ou revisando um trecho de código. E ai, do nada, a nossa intuição nos diz que conseguimos super simplificar nosso código. Ou, se estamos familiarizados com o paradigma de programação imperativa, parece que existe um jeito “funcional” de resolver o problema.

Esse artigo foi escrito para nos desbloquear nesse tipo de situação. Afinal, conseguimos tirar inspiração de qualquer lugar. É apenas uma questão de achar as dicas certas para achar a solução. E quanto mais perto a dica for da solução, melhor.

Passaremos por um conjunto de tarefas de codificação bem conhecidas ordenadas em dificuldade. O autor usou TypeScript com Node 10 para testar cada solução.

1. Teste Unitários Mockados

Problema

Ao rodar os teste localmente, sobre uma função assíncrona, você quer que os dados mockados sejam utilizados.

Solução não funcional

Usar um if-else para determinar qual função rodar, a assíncrona ou a mockada.

Solução funcional

Atribua as funções assíncrona e mockada à variáveis. Então, use um operador ternário para determinar qual função utilizar com base no ambiente atual.

Versão de um trecho de código do autor.

2. Teste A/B ou Experimentação

Problema

Você quer mostra duas ou mais versões de uma aplicação web: versões A e B.

Solução não funcional

Usar um if-else para determinar qual versão mostrar.

Solução funcional

Atribua as funções que expõem essas versões à variáveis. Crie um objeto que mapeie as variáveis de função para às versões correspondentes. Use a versão como a chave e a variável de função como valor. Mostre a versão usando o objeto map.

Versão de um trecho de código do autor.

3. Executar uma Série de Funções Sobre um Objeto

Problema

Você tem uma série de funções que pegam um objeto, executam uma série de atualizações nesse objeto e retornam um objeto transformado ou atualizado:

Versão de um trecho de código do autor.

Solução não funcional

Você executa uma função por linha. Por exemplo, inicializa um objeto funcionario com um id. E ai uma série de funções atualizarão o objeto funcionario e retornam um objeto funcionario atualizado.

Versão de um trecho de código do autor. O código executa as funções começando na linha 17.

O código acima faz todo o trabalho. Você está aplicando cada função em uma linha e mudando o funcionario. Imagine que você precisa adicionar uma nova função. Você poderia adicionar uma nova linha para chamar a função e passaria o objeto funcionario como argumento. Nada mal, mas você consegue fazer melhor.

Solução funcional

O jeito funcional é atribuir as funções a um array, iterar sobre ele usando um forEach e executar cada função sobre o argumento. Note que o argumento funcionario passado a cada execução é o retorno da função anterior. Você estará usando as funções na mesma ordem que elas foram colocadas no array.

Versão de um trecho de código do autor. Na linha 17 as funções são colocadas em um array.

Se você precisar adicionar outra função a ser executada, adicione outra entrada no array funcoesFuncionarioArray. Você não precisará atribuir novamente o valor retornado para funcionario e passa-lo como um argumento para a próxima função.

4. Calculadora Simples

Problema

Você quer implementar uma calculadora simples que aceita dois números

Solução não funcional

A solução orientada a objetos é criar uma classe Calculadora que contenha as operações como métodos, no caso soma e subtracao. Se você quiser adicionar mais operações, terá que atualizar a classe para que cada operação tenha um método correspondente. Outro jeito é escrever uma função para cada operação. Por exemplo, implementar as funções calculaSoma()e calculaDiferenca().

Solução funcional

Criar uma função calcular()que aceita uma função de operação (ex. soma()) como argumento que será usado para fazer o cálculo desejado. A função que faz o cálculo é também auto-contida e você pode usá-la sem ser passada como um argumento.

Versão de um trecho de código do autor. Passar a função da operação como argumento para a função que calcula.

5. Calculadora de Conversão

Problema

Converter metros em uma unidade de medida imperial como milha, jarda, e polegada. As funções de conversão pegam um valor x em metros e retornam o valor convertido numa unidade de medida imperial com seu símbolo correspondente.

Solução não funcional

Escrever uma função para cada conversão.

Versão de um trecho de código do autor. As conversões começam na linha 6.

O código duplicado nas funções de conversão é evidente no exemplo acima. Multiplicar e formatar strings é repetido em cada função.

Solução funcional

Tente resolver isso criando uma função que lidará de forma diferente com as conversões por maximizar a reutilização de código entre funções com comportamentos ligeiramente diferentes.

A função criarConversor() abaixo aceita a o valor de conversão v e um símbolo s como argumentos. Ela retorna a função que pega um argumento x, que é o valor a ser convertido.

const criarConversor = (v: number, s: string) => 
(x: number) => `${x * v} ${s}`

Reutilize a função criarConversor acima para criar as funções de conversão.

const metrosParaMilhas = criarConversor(0.0006213689, 'mi')
console.log(metrosParaMilhas(1000))

Assim, você vai maximizar o reuso de código bem como sua manutenibilidade.

Versão de um trecho de código do autor.

6. Validar Argumentos de Funções

Problema

Você tem uma função que executa uma operação de subtração, sobre um minuendo e um subtraendo, e retorna a diferenca. O valor retornado só pode ser um número não negativo. Assim, você precisará validar seus argumentos de forma que o minuendo deve sempre ser maior ou igual subtraendo: minuendo >= subtraendo.

const subtracao = (minuendo: number, subtraendo: number): number => 
minuendo - subtraendo

De acordo com Class Ace:

- “minuendo — Esse é o maior número, ou o todo, do qual uma parte será retirada.

- subtraendo — Essa é a parte retirada do minuendo.

- diferença — Essa é a parte que sobra da subtração.”

Solução não funcional

Tipicamente, validaríamos os argumentos por adicionar um condicional com um retorno prévio.

Versão de um trecho de código do autor.

O código acima funciona. Porém, a função está fazendo mais de uma coisa. Ao invés de apenas retornar a diferença, ela também valida os argumentos. Isso viola o princípio de responsabilidade única (single-responsibility) do SOLID.

Solução funcional

Primeiro, criamos uma função separada para validar os argumentos.

Versão de um trecho de código do autor.
const diferenca = (minuendo: number, subtraendo: number): number => 
minuendo - subtraendo

Criamos uma função diferencaPositiva() que chama a função de validação minuendoMaiorQueSubtraendo() com a função subtracao() como seu argumento. A função diferencaPositiva() será chamada para assegurar que os argumentos são válidos antes de executar a subtração.

const diferencaPositiva =    minuendoMaiorQueSubtraendo(subtracao)

Cada função está fazendo apenas uma coisa para resolver o problema.

Versão de um trecho de código do autor. Uma solução funcional para validar os argumentos ao mesmo tempo que respeitamos o princípio da responsabilidade única.

7. Contador de Palavras

Problema

Você tem uma string de palavras ou um poema numa variável. Você quer contar o número de vezes que uma palavra ocorre na string. O problema é análogo a contar o número de ocorrências de um determinado elemento num array.

const poema: string = 
`Hold fast to dreams
For if dreams die
Life is a broken-winged bird
That cannot fly
Hold fast to dreams
For when dreams go
Life is a barren field
Frozen with snow`.replace(/(\r\n|\n|\r)/gm, ' ').toLowerCase()

Para simplificar a string, substitua as quebras de linha por espaços e transforme em caixa baixa (lowercase).

Solução não funcional

Usar um hashmap para rastrear o número de ocorrência das palavras.

Versão de um trecho de código do autor.

Solução funcional

Use um reduce com o hashmap contadorDePalavras como acumulador e palavra como um elemento a se processado.

Use o operador spread (…) para extrair a chave e o valor do hashmap contadorDePalavras.

Ai use um ternário para verificar se a palavra já está no hashmap. Se ela estiver no hashmap, incremente o valor. Do contrário, atribua 1, que significa que a palavra apareceu pela primeira vez.

Versão de um trecho de código do autor. A solução funcional é mais compacta e fácil de ler se você estiver acostumado à programação funcional.

8. Achar Anagramas

Problema

“Um anagrama é uma espécie de jogo de palavras criado com a reorganização das letras de uma palavra ou expressão para produzir outras palavras ou expressões, utilizando todas as letras originais exatamente uma vez.” — Wikipedia

Dada uma palavra, você quer encontrar seus anagrama(s) em um dado array de palavras.

Por exemplo, você tem o array de palavras abaixo, com os correspondentes anagramas nos comentários.

const palavras: Array<string> = [
'rat', // tar
'car', // arc
'below', // elbow
'taste', // state
'cried', // cider
'study', // dusty
'thing', // night
'chin', // inch
'grab', // brag
'act', // cat
'robed', // bored
'vase', // save
'glean', // angel
'desserts', // stressed
]

A entrada pode ser um dos possíveis anagramas.

const entrada: string = 'save'

Solução não funcional

Existem muitos exemplos de soluções não funcionais para esse problema na internet, então não vamos falar sobre todas as possibilidades.

Solução funcional

Você pode resolver esse problema com uma combinação das funções built-in filter e reduce do JavaScript. Reaproveite a mesma função contadorDePalavrasFuncional do exemplo #7 que usa reduce, mas ao invés de contar palavras, agora você contará letras numa palavra.

const contadorDeLetrasFuncional = (palavra: Array<string>) =>                     palavra.reduce((contadorDeLetras: Map<string, number>, letra: string) => ({
...contadorDeLetras,
[letra]: contadorDeLetras[letra] ? contadorDeLetras[letra] + 1 : 1,
}), new Map<string, number>());

Use a função filter para filtrar as palavras que tem as mesmas letras e tamanho do seu valor entrada.

const achaAnagramas = (palavra: string, palavras: Array<string>): Array<string> => {
return palavras
.filter(entrada => temMesmoTamanhoELetras(palavra, entrada))
.filter(anagrama => anagrama !== palavra);
}

A solução final será a seguinte:

Versão de um trecho de código do autor. A solução usa uma combinação das funções filter() e reduce().

9. Validação de um Form

Problema

Você tem um form que aceita uma entrada do usuário e você precisa validá-la. Uma validação simples poderia ser que o username tenha pelo menos três caracteres ou que o usuário deve informar uma email válido.

const entradas: Object = {
username: 'ar',
email: 'me@',
}

Solução não funcional

Existem muitos exemplos de soluções não funcionais para esse problema na internet, então não vamos falar sobre todas as possibilidades.

Solução funcional

Comece com um hashmap de critérios a serem aplicados sobre as entradas onde a chave é o campo e o valor é um array de critérios. Por exemplo:

Versão de um trecho de código do autor.

As funções temTamanhoMinimo e eUmEmailValido verificam se a entrada e retornam uma mensagem de erro.

Versão de um trecho de código do autor.

Use uma combinação de reduce e map para acumular as mensagens de erro retornadas ao testar cada entrada contra os correspondentes critérios de validação. Use filter para excluir as strings vazias.

Versão de um trecho de código do autor. A solução usa reduce, map e filter.

Conclusão

Recapitulando:

  • Exemplos 1, 2, e 3 são bons exemplos de como tratar funções como dados.
  • Exemplo 4 mostra que é possível usar funções como argumento.
  • Exemplo 5 mostra que é possível retornar funções.
  • Exemplo 6 mostra como pode-se usar funções de ordem superior.
  • Exemplos 7–9: usa uma combinação dos conceitos anteriores somados à funções built-in comomap, reduce e filter.

Não digo que vocês sempre devem usar as soluções funcionais delineadas acima quando se depararem com os mesmos problemas. Às vezes é uma questão de preferência ou estilo de codificação. Também é de ajuda considerar outros fatores antes de sair desenvolvendo a solução, como seu repositório de código atual, os membros do seu time e os guidelines de código que seguem.

Espero que sua jornada com programação funcional esteja sendo divertida até agora!

Você encontrará os exemplos de código deste artigo em:

Donativos

Se desejar apoiar voluntariamente meu projeto pessoal de traduzir do inglês artigos que tenham um conteúdo relevante para engenheiros e desenvolvedores que falam português (ou apenas me pagar uma cerveja), faz um pix ai:

R$ 2,00 (cerveja bem barata)

Chave Pix: 95303051-e84b-4e0a-bd75-d285420107e4

--

--

Alison Miazaki

Engenheiro de computação apaixonado por aprender e ensinar.