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 Javascript em seus projetos.
Antes de começar a abordar os recursos específicos da linguagem de programação funcional Javascript, temos de ter em mente alguns conceitos base do paradigma da linguagem funcional, bem como suas aplicações práticas.
Neste post, iremos falar basicamente sobre três assuntos:
- efeitos colaterais;
- imutabilidade;
- composição de funções.
O que é função?
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:
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:
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 de programação funcional Javascript está 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:
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:
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, na programação funcional do 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:
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 de programação funcional Javascript , 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?
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 programação funcional no Javascript, 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 Javascript, 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:
- Learn You a Haskell for Great Good!!;
- A practical introduction to functional programming;
- Funfunfunction (canal no YouTube);
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 Martins
Analista de Sistemas em Blip