Entendendo Programação Funcional em JavaScript de uma vez
Você já percebeu que cada vez mais o termo Programação Funcional vem sendo usado pela comunidade?
No meu último post, por exemplo: O que TODO desenvolvedor JavaScript precisa saber, um dos pontos que gerou mais dúvida foi justamente o da Programação Funcional.
Continue lendo esse artigo para aprender:
1. Quais as vantagens de usar a Programação Funcional
2. Como usá-la tanto em ES5 quanto em ES6
3. O que são Pure Functions e Higher-Order Functions
4. Qual a diferença entre Map, Filter e Reduce
5. O que é Currying
6. Como compor funções de maneira eficaz
Para entender as verdadeiras motivações, temos obrigatoriamente que voltar aos conceitos básicos.
A função abaixo possui inputs e outputs bem definidos:
Ela recebe como parâmetro uma variável x e retorna um int que é a multiplicação de x com ele mesmo.
A função abaixo porém, não possui inputs e outputs tão bem definidos:
Ela não recebe nada como parâmetro e retorna o que parece ser uma data processada mas não temos como ter certeza.
Um ponto que vale ser reforçado: só porque não declaramos explicitamente os inputs e outputs dessa função não quer dizer que a mesma não os tenha. Eles apenas estão ocultos. E isso pode gerar um dos piores problemas nas aplicações modernas: os Efeitos Colaterais (side-effects).
Funções como as de cima que possuem inputs e outputs ocultos e podem gerar side-effects são chamadas de Funções Impuras (impure functions). Outra característica importante delas é que se invocarmos uma função impura diversas vezes, o retorno dela nem sempre será o mesmo. O que dificulta a manutenção e os testes na sua aplicação.
Funções Puras (pure functions) por outro lado, como o primeiro exemplo desse post, tem inputs e outputs declarados e não geram side-effects. Além disso, o retorno de uma função pura dado um parâmetro será sempre o mesmo. Obviamente os seus testes serão mais fáceis de desenvolver, assim como a manutenção da sua aplicação.
E por que estamos falando sobre isso?
Porque escrever funções puras e remover side-effects é a base da Programação Funcional.
Agora que temos uma ideia melhor da motivação de se usar Programação Funcional, podemos começar a ver os casos reais e aprender na prática como usá-la.
1) Higher-Order Functions
Matematicamente falando, funções que operam sobre outras funções ou as recebendo como parâmetro ou as retornando são chamadas de Higher-Order Functions.
Essas funções nos permitem fazer abstrações não apenas de valores mas também de ações, como no exemplo abaixo:
A função calculate recebe três parâmetros. O primeiro é uma função qualquer que será invocada passando como parâmetro x e y.
Pensando em um cenário em que precisamos tanto de uma soma quanto de uma multiplicação, podemos pensar na solução dessa forma em ES5:
Ou dessa, bem mais curta, em ES6:
Higher-order functions estão em todos os lugares no ecossistema do JavaScript. Se você já usou testes unitários com Jasmine ou Mocha, então o trecho abaixo deve ser familiar:
Percebemos que o segundo parâmetro tanto de describe quanto de it são funções. Por definição ambas são higher-order functions.
Outro exemplo também é o bom e velho jQuery. Podemos perceber que praticamente todo o código gerado por ele era composto de higher-order functions, como o exemplo abaixo:
Em aplicações desenvolvidas com AngularJS também não é diferente, observe atentamente a definição de um controller:
Agora que já temos conhecimento dos fundamentos: pure functions e higher-order functions podemos nos aprofundar um pouco mais…
2) Map
A função map invoca um callback e retorna um novo array com o resultado desse callback aplicado em cada item do array inicial.
Imaginando um cenário em que temos um array de inteiros e precisamos do quadrado de cada valor desse array, podemos fazer dessa forma bem simples usando a função map em ES5:
Ou assim em ES6:
Nesse outro cenário abaixo, percebemos o reaproveitamento de código que podemos conseguir ao usar o map.
Possuímos dois arrays de objetos diferentes, porém ambos tem o campo name, e precisamos de uma função que retorne um novo array apenas com os names dos objetos:
Em ES6:
Podemos melhorar ainda mais esse trecho de código alterando as funções byName e byNames para que o atributo name não esteja mais tão acoplado. Podemos simplesmente receber como parâmetro qualquer atributo e aplicá-lo a função. Fica como exercício :)
3) Filter
A função filter é bem semelhante ao map: ela também recebe um callback como parâmetro e também retorna um novo array, a única diferença é que filter, como o próprio nome diz, retorna um filtro dos elementos do array inicial baseado na função de callback.
Imaginando que temos um array de inteiros e desejamos retornar apenas aqueles que são maiores do que 4. Podemos resolver assim usando o filter com ES5:
Ou com ES6:
Outro exercício é uma melhoria na função isBiggerThanFour, deveríamos alterá-la para receber como parâmetro qualquer inteiro que desejamos fazer a comparação.
4) Reduce
Uma das funções que mais gera dúvidas é o reduce. Ele recebe como parâmetro um callback e um valor inicial, com o objetivo de reduzir o array a um único valor. O cenário mais comum para explicar o reduce é uma soma:
Com ES6 seria assim:
O primeiro parâmetro é a função que será aplicada, no caso uma soma. E o segundo parâmetro é o valor inicial. Se por algum motivo precisássemos começar a soma com 10, faríamos dessa forma:
Mas o reduce não serve apenas para somas, podemos também trabalhar com strings. Imaginando que nós temos um array de meses e precisamos retornar o meses dessa forma: JAN/FEV/MAR … / DEZ.
Podemos fazer assim:
Não era bem o que a gente queria inicialmente.
Nós queríamos isso: JAN/FEV/MAR … / DEZ
Mas obtivemos isso: /JAN/FEV/MAR … / DEZ
Devemos alterar nossa função monthsShortener para adicionar uma condição que faça a prevenção desse erro:
Feito! E também na versão em ES6:
[UPDATE — 03/04/2016]
Verificar o comentário do Marcus Tenório para um algoritmo melhor
5) Currying
A técnica de transformar uma função com múltiplos parâmetros em uma sequência de funções que aceitam apenas um parâmetro é chamada de Currying.
Se na teoria ficou confuso, na prática seria transformar isso:
Nisso:
A princípio parece que estamos apenas adicionando mais dificuldade sem nenhum ganho. Porém temos uma grande vantagem: transformar 0 código em pequenos pedaços mais expressivos e com maior reuso.
Pensando em uma aplicação que possui diversos trechos do código uma soma com 5 e outra com 10, podemos usar a segunda versão da função add dessa forma:
Mais um exemplo seria um Hello World simples com uma curried function. Podemos implementá-lo desse jeito com ES5:
Ou com ES6:
6) Compose
Podemos compor funções pequenas para gerar outras mais complexas de forma bem fácil em JavaScript. A vantagem é o poder de usar essas funções mais complexas, de forma simples, em toda aplicação. Ou seja, aumentamos o reuso.
Por exemplo, em uma aplicação em que necessitamos de uma função para transformar uma string passada pelo usuário em um grito: mudar para caracteres maiúsculos e adicionar uma exclamação no final. Podemos fazer assim em ES5:
Ou em ES6:
Se você gostou do post não se esqueça de dar um ❤ aqui embaixo!
E se quiser receber de antemão mais posts como esse, assine nossa newsletter.
Seja um apoiador, doe BitCoins: 1BGVKwjwQxkr3w1Md2X8WHAsyRjDjyJiPZ
JSCasts
É difícil encontrar conteúdo bom e atualizado em português. Com isso em mente criamos o JSCasts, onde você vai se manter em dia com o JavaScript e todo o seu ecossistema de forma fácil e interativa.
Cursos:
Obrigado por ler! ❤