Introdução à Programação Funcional: Paradigmas JavaScript
Publicados: 2022-03-11A programação funcional é um paradigma de construção de programas de computador usando expressões e funções sem mutação de estado e dados.
Ao respeitar essas restrições, a programação funcional visa escrever um código mais claro de entender e mais resistente a bugs. Isso é conseguido evitando o uso de instruções de controle de fluxo ( for
, while
, break
, continue
, goto
) que tornam o código mais difícil de seguir. Além disso, a programação funcional exige que escrevamos funções puras e determinísticas que são menos propensas a apresentar bugs.
Neste artigo, falaremos sobre como fazer programação funcional usando JavaScript. Também exploraremos vários métodos e recursos JavaScript que tornam isso possível. No final, exploraremos diferentes conceitos associados à programação funcional e veremos por que eles são tão poderosos.
Antes de entrar na programação funcional, porém, é preciso entender a diferença entre funções puras e impuras.
Funções puras vs. impuras
Funções puras recebem alguma entrada e fornecem uma saída fixa. Além disso, eles não causam efeitos colaterais no mundo exterior.
const add = (a, b) => a + b;
Aqui, add
é uma função pura. Isso ocorre porque, para um valor fixo de a
e b
, a saída será sempre a mesma.
const SECRET = 42; const getId = (a) => SECRET * a;
getId
não é uma função pura. A razão é que ele usa a variável global SECRET
para calcular a saída. Se SECRET
for alterado, a função getId
retornará um valor diferente para a mesma entrada. Portanto, não é uma função pura.
let id_count = 0; const getId = () => ++id_count;
Essa também é uma função impura, e também por algumas razões: (1) ela usa uma variável não local para calcular sua saída e (2) ela cria um efeito colateral no mundo exterior modificando uma variável nesse mundo.
Isso pode ser problemático se tivermos que depurar esse código.
Qual é o valor atual de id_count
? Quais outras funções estão modificando id_count
? Existem outras funções que dependem de id_count
?
Por esses motivos, usamos apenas funções puras em programação funcional.
Outro benefício das funções puras é que elas podem ser paralelizadas e memorizadas. Dê uma olhada nas duas funções anteriores. É impossível paralelizá-los ou memorizá-los. Isso ajuda na criação de código de alto desempenho.
Os Princípios da Programação Funcional
Até agora, aprendemos que a programação funcional depende de algumas regras. Eles são os seguintes.
- Não altere dados
- Use funções puras: saída fixa para entradas fixas e sem efeitos colaterais
- Usar expressões e declarações
Quando satisfazemos essas condições, podemos dizer que nosso código é funcional.
Programação Funcional em JavaScript
O JavaScript já possui algumas funções que possibilitam a programação funcional. Exemplo: String.prototype.slice, Array.protoype.filter, Array.prototype.join.
Por outro lado, Array.prototype.forEach, Array.prototype.push são funções impuras.
Pode-se argumentar que Array.prototype.forEach
não é uma função impura por design, mas pense nisso - não é possível fazer nada com ela, exceto alterar dados não locais ou causar efeitos colaterais. Assim, não há problema em colocá-lo na categoria de funções impuras.
Além disso, o JavaScript tem uma declaração const, que é perfeita para programação funcional, pois não estaremos alterando nenhum dado.
Funções puras em JavaScript
Vejamos algumas das funções puras (métodos) fornecidas pelo JavaScript.
Filtro
Como o nome sugere, isso filtra a matriz.
array.filter(condition);
A condição aqui é uma função que obtém cada item do array, e deve decidir se deve manter o item ou não e retornar o valor booleano verdadeiro para isso.
const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]
Observe que filterEven
é uma função pura. Se fosse impuro, teria tornado impura toda a chamada de filtro.
Mapa
map
mapeia cada item do array para uma função e cria um novo array com base nos valores de retorno das chamadas de função.
array.map(mapper)
mapper
é uma função que recebe um item de um array como entrada e retorna a saída.
const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]
Reduzir
reduce
reduz a matriz a um único valor.
array.reduce(reducer);
reducer
é uma função que pega o valor acumulado e o próximo item no array e retorna o novo valor. É chamado assim para todos os valores na matriz, um após o outro.
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6
Concatenar
concat
adiciona novos itens a um array existente para criar um novo array. É diferente de push()
no sentido de que push()
altera os dados, o que os torna impuros.

[1, 2].concat([3, 4]) // [1, 2, 3, 4]
Você também pode fazer o mesmo usando o operador spread.
[1, 2, ...[3, 4]]
Object.assign
Object.assign
copia valores do objeto fornecido para um novo objeto. Como a programação funcional é baseada em dados imutáveis, nós a usamos para criar novos objetos baseados em objetos existentes.
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
Com o advento do ES6, isso também pode ser feito usando o operador spread.
const newObj = {...obj};
Criando sua própria função pura
Podemos criar nossa função pura também. Vamos fazer um para duplicar uma string n
vezes.
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
Esta função duplica uma string n
vezes e retorna uma nova string.
duplicate('hooray!', 3) // hooray!hooray!hooray!
Funções de ordem superior
Funções de ordem superior são funções que aceitam uma função como argumento e retornam uma função. Muitas vezes, eles são usados para adicionar à funcionalidade de uma função.
const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };
No exemplo acima, criamos uma função de ordem superior withLog
que recebe uma função e retorna uma função que registra uma mensagem antes que a função encapsulada seja executada.
const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7
withLog
HOF também pode ser usado com outras funções e funciona sem conflitos ou sem escrever código extra. Esta é a beleza de um HOF.
const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!
Pode-se também chamá-lo sem definir uma função de combinação.
withLog(hype)('Sale'); // calling hype // Sale!!!
Escovando
Curry significa dividir uma função que recebe vários argumentos em um ou vários níveis de funções de ordem superior.
Vamos pegar a função add
.
const add = (a, b) => a + b;
Quando vamos curá-lo, nós o reescrevemos distribuindo argumentos em vários níveis como segue.
const add = a => { return b => { return a + b; }; }; add(3)(4); // 7
O benefício do currying é a memorização. Agora podemos memorizar certos argumentos em uma chamada de função para que possam ser reutilizados posteriormente sem duplicação e re-computação.
// assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);
Isso é certamente melhor do que usar os dois argumentos em todos os lugares.
// (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());
Também podemos reformatar nossa função curry para parecer sucinta. Isso ocorre porque cada nível da chamada da função currying é uma instrução de retorno de linha única. Portanto, podemos usar funções de seta no ES6 para refatorá-lo da seguinte maneira.
const add = a => b => a + b;
Composição
Em matemática, a composição é definida como a passagem da saída de uma função para a entrada de outra, de modo a criar uma saída combinada. O mesmo é possível na programação funcional, pois estamos usando funções puras.
Para mostrar um exemplo, vamos criar algumas funções.
A primeira função é range, que recebe um número inicial a
e um número final b
e cria uma matriz que consiste em números de a
a b
.
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
Então temos uma função multiplicar que pega um array e multiplica todos os números nele.
const multiply = arr => arr.reduce((p, a) => p * a);
Usaremos essas funções juntas para calcular o fatorial.
const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720
A função acima para cálculo fatorial é semelhante a f(x) = g(h(x))
, demonstrando assim a propriedade de composição.
Palavras finais
Passamos por funções puras e impuras, programação funcional, os novos recursos JavaScript que ajudam com isso e alguns conceitos-chave em programação funcional.
Esperamos que esta peça desperte seu interesse em programação funcional e possivelmente o motive a experimentá-la em seu código. Temos certeza de que será uma experiência de aprendizado e um marco em sua jornada de desenvolvimento de software.
A programação funcional é um paradigma bem pesquisado e robusto de escrever programas de computador. Com a introdução do ES6, o JavaScript permite uma experiência de programação funcional muito melhor do que nunca.