Como aplicar os conceitos da programação funcional no Javascript

Neste artigo, vamos tentar abordar de forma prática (e por que não teórica) o motivo pelo qual você, que usa Javascript no seu dia-a-dia de trabalho, deve começar a explorar os benefícios da programação funcional no Javascript nos seus projetos.

Antes de começar a abordar os recursos específicos da linguagem, temos de ter em mente alguns conceitos base do paradigma funcional, bem como suas aplicações práticas. Neste post, iremos falar basicamente sobre três assuntos: efeitos colaterais, imutabilidade e composição de funções.

Antes de mais nada, vamos pedir ajuda para a matemática para esclarecer o conceito de uma função. Uma função pode ser entendida como a relação entre dois conjuntos quaisquer (A e B) e uma regra que permite associar um conjunto ao outro. Sendo assim, em linguagem matemática:

f: A -> B

Vamos chamar o conjunto A de domínio e o B de contradomínio. Isso significa que, se o meu domínio for sempre A, meu contradomínio será sempre B. Em Javascript, podemos dizer então que na função abaixo, se o meu domínio for 2, meu contradomínio sempre será 4:

programação funcional no javascript

Isso é o que chamamos de uma função pura, ou seja, uma função que não altera variáveis fora do seu escopo (não possui efeito colateral) e sempre retorna o mesmo valor quando são passados os mesmos argumentos.

Efeitos colaterais

Um efeito colateral é qualquer alteração no estado da aplicação que seja percebida fora do escopo da função chamada. Isso pode gerar alguns problemas como imprevisibilidade e complexidade no raciocínio. Efeitos colaterais em Javascript são comumente observados nos seguintes casos:

  • Console.log;
  • Alteração no DOM (escritas na página e afins);
  • Escrita em arquivos;
  • Chamadas a outras funções com efeitos colaterais.

Para exemplificar o que seria uma “função impura” com efeitos colaterais, vou mostrar o seguinte código:

programação funcional no javascript

Neste caso, podemos perceber que a função “sum” altera a variável “result” que está fora do seu escopo, o que a torna impura e com efeitos colaterais. Para evitar esses casos, devemos sempre ter em mente que uma função deve ter sempre um domínio claro e um contradomínio único para uma mesma entrada de parâmetros.

Fica muito mais fácil na hora de debugar quando temos a certeza de que as variáveis de um determinado escopo não correm o menor risco de serem alteradas por outras funções que podem não fazer o menor sentido na execução do programa.

Porém, eu diria que, em Javascript, é praticamente impossível escrever uma aplicação inteira que não possua nenhum efeito colateral, visto que constantemente estamos manipulando o DOM, chamando funções de bibliotecas externas, etc. Entretanto, sempre que possível, podemos evitar diversos casos de efeitos colaterais utilizando as funções puras.

Imutabilidade

Outro conceito importante do paradigma funcional e totalmente atrelado ao uso de funções impuras vistas no tópico anterior é o que chamamos de imutabilidade. Imutabilidade nada mais é do que evitar alterar valores das variáveis, seja por meio de funções ou de atribuições simples. Veja o exemplo a seguir:

programação funcional no javascript

Veja que, neste caso, estamos alterando diretamente o valor do objeto “blipGreeting”. Reescrevendo nosso código para uma função imutável, teríamos o seguinte exemplo:

programação funcional no javascript

Perceba que, no exemplo acima, em vez de modificarmos o valor da variável blipGreeting, criamos uma nova variável com o novo valor desejado e a retornamos em seguida.

Essa abordagem em grandes aplicações pode ser extremamente vantajosa, uma vez que, lendo o código, você pode ter a certeza de que o valor da variável naquele ponto será sempre aquele, e que, se em algum momento for necessário um outro valor, uma nova variável será criada para isso. Isso nos traz uma maior previsibilidade do que está acontecendo e maior facilidade ao debugar.

Sendo assim, no Javascript devemos evitar alguns métodos mutáveis como push, pop, unshift, shift, e até mesmo os laços de repetição for, foreach e while. No caso da manipulação de arrays, existem métodos alternativos do Javascript que sempre irão retornar um novo valor em vez de modificar algum existente, como é o caso do map, reduce e concat.

No caso dos laços de repetição, uma boa alternativa é utilizar o próprio map ou reduce para iterar sobre os itens de um array. Neste post, o autor dá várias alternativas aos métodos mutáveis do Javascript.

Composição de funções

No paradigma funcional, temos o que chamamos de programação declarativa. Em linhas gerais, podemos distinguir a abordagem declarativa da programação imperativa pela seguinte definição: a programação declarativa foca no “o que” deve ser feito e a imperativa no “como” deve ser feito.

Sempre quando quero esclarecer esse conceito, gosto de utilizar o exemplo da receita de bolo. Se fôssemos escrever um código que fizesse um bolo utilizando a programação imperativa, por exemplo, iríamos descrever um passo-a-passo de como fazer o bolo (adicione os ovos, depois adicione açúcar, em seguida o leite…). Já na programação declarativa, iríamos descrever como gostaríamos que o nosso bolo ficasse (quero um bolo de chocolate, com pouco açúcar, com 30cm de diâmetro…).

Além de facilitar a leitura, isso ajuda bastante na hora dos testes. É muito mais fácil eu testar se “a calda de chocolate foi adicionada” do que testar “se a receita deu certo”. Ficou claro?

Utilizando novamente o conceito matemático, na 5ª ou 6ª série aprendemos que a notação f(g(h(x))) pode ser lida como f composta em g composta em h. Sendo assim, o resultado da função h(x) será passado como parâmetro para a função g(x), que terá o seu resultado passado como parâmetro para a função f(x), começando sempre pela função mais interna até chegar na mais externa. Mas como isso fica no código?

programação funcional no javascript

No código acima, o que fizemos foi pegar um array com alguns números, dividir todos eles por 2, pegar o array resultante e multiplicar todos os números por 3. No resultado final, aplicamos a função sumTwo que soma 2 a todos os números do array.

Dessa forma, o código fica reaproveitável, previsível e super fácil de testar. Podemos ainda refinar um pouco mais o nosso código e criar uma função “compose”, responsável por compor todas as funções passadas via parâmetro com base em um valor inicial, ficando da seguinte forma:

Dessa forma, o código fica um pouco mais legível e ainda mais declarativo.

Considerações finais

Na computação, não existe verdade absoluta, pois tudo depende do contexto. Embora o Javascript não seja uma linguagem puramente funcional, vimos que é possível aplicar os conceitos básicos do paradigma funcional na linguagem assim como em qualquer outras.

Vimos também os desafios de tratar imutabilidade e percebemos que, na maioria das vezes, códigos declarativos são mais fáceis de serem debugados, possuem uma melhor testabilidade e uma maior corretude.

Entender os conceitos da programação funcional antes de entender sua aplicação em qualquer linguagem é fundamental. Por isso, se você tem interesse no assunto e gostaria de fazer estudos um pouco mais aprofundados, vou deixar aqui uma lista de artigos que me ajudaram em algum momento e que podem direcionar melhor a sua pesquisa:

Existem também algumas linguagens funcionais que compilam para Javascript e que são excelentes para praticar os conceitos desse paradigma:

Curtiu o post? Quer ver mais conteúdos sobre o assunto por aqui? Deixe suas sugestões nos comentários!


samuel post programação funcional no javascript

Samuel Martins

Analista de Sistemas na Take

 

Leia mais:

Por que usar TypeScript como linguagem de programação

Performance no front-end: como fazer a sua aplicação web voar

Expectativas das empresas com TI: como apresentar o setor?