Programação Funcional em JavaScript: de A a Z
Programação Funcional (PF) é um paradigma que trata a computação como a avaliação de funções matemáticas e evita a mudança de estado e dados mutáveis. No contexto do JavaScript (Node.js), a PF se torna uma ferramenta poderosa para criar código previsível, testável e escalável. Neste artigo, vamos explorar os conceitos-chave da PF, desde o básico até técnicas avançadas, apoiando a teoria com exemplos práticos.
Fundamentos: Funções Puras e Imutabilidade
Na base da PF estão dois princípios: funções puras e imutabilidade (dados imutáveis).
Função pura sempre retorna o mesmo resultado para os mesmos argumentos e não possui efeitos colaterais (não altera variáveis externas, não imprime no console, não faz requisições a APIs).
// Função puraconst add = (a, b) => a + b;
// Função impura (efeito colateral — alteração de variável externa)let counter = 0;const increment = () => counter++;Imutabilidade significa que os dados não são alterados diretamente, mas sim criadas novas cópias com as alterações necessárias. Em JavaScript, usamos métodos que retornam novos objetos/arrays.
// Atualização imutável de objetoconst user = { name: 'Alice', age: 30 };const updatedUser = { ...user, age: 31 }; // Criamos um novo objeto
// Atualização imutável de arrayconst numbers = [1, 2, 3];const newNumbers = [...numbers, 4]; // [1, 2, 3, 4]Closures e Funções de Alta Ordem
Closure é uma função que "lembra" seu escopo léxico mesmo após a função externa ter terminado sua execução. É a base para muitos padrões da PF.
const createMultiplier = (factor) => { // Função interna — closure return (number) => number * factor;};
const double = createMultiplier(2);console.log(double(5)); // 10Funções de alta ordem são funções que recebem outras funções como argumentos ou as retornam. Exemplos clássicos são map, filter, reduce.
const numbers = [1, 2, 3, 4, 5];
// map — transforma cada elementoconst doubled = numbers.map(n => n * 2); // [2, 4, 6, 8, 10]
// filter — seleciona elementos conforme condiçãoconst even = numbers.filter(n => n % 2 === 0); // [2, 4]
// reduce — reduz o array a um único valorconst sum = numbers.reduce((acc, n) => acc + n, 0); // 15Composição de Funções e Currying
Composição de funções é a combinação de várias funções em uma, onde o resultado de uma função é passado como entrada para a próxima. Isso permite construir operações complexas a partir de blocos simples e reutilizáveis.
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x);
const trim = (str) => str.trim();const toLowerCase = (str) => str.toLowerCase();const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const processName = compose(capitalize, toLowerCase, trim);console.log(processName(' JOHN DOE ')); // 'John doe'Currying é a transformação de uma função com vários argumentos em uma sequência de funções, cada uma recebendo um único argumento. Isso facilita a aplicação parcial e a composição.
// Função comumconst multiply = (a, b, c) => a * b * c;
// Versão curriedconst curriedMultiply = (a) => (b) => (c) => a * b * c;
console.log(curriedMultiply(2)(3)(4)); // 24
// Aplicação parcialconst multiplyBy2 = curriedMultiply(2);const multiplyBy2And3 = multiplyBy2(3);console.log(multiplyBy2And3(5)); // 30