voltar aos artigosfunctional programming
$cd~/artigos/currying-composition-javascript-padroes-funcionais
Functional Programming
série 33 Conceitos JS·parte 29 de 38

Currying & Composition: Montando Funções como LEGO

24 minVanderlei José Alamino

"A simplicidade é a sofisticação final. E nada é mais simples do que uma função que faz uma coisa só — combinada com outras que fazem uma coisa só."

Currying transforma f(a, b, c) em f(a)(b)(c) — um argumento por vez, lembrado por closures. Composição encadeia funções: a saída de uma vira entrada da próxima, como uma linha de montagem.

snippetJavaScript
const add5ThenDouble = pipe(add(5), multiply(2));add5ThenDouble(10);  // 30

Neste guia da série 33 Conceitos JS — projeto open source criado por Leonardo Maldonado — você fecha a trilha Functional Programming com os padrões que unem HOFs, pureza e pipelines de dados.

Pré-requisitos: Closures, Higher-Order Functions e Pure Functions.

currying-composition-javascript-padroes-funcionais.meta.jsonjson

// metadados do artigo

{
  "tag": "functional programming",
  "readTime": "24 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 29,
    "total": 38
  }
}
abrir fonte original
currying-composition-javascript-padroes-funcionais.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

Analogia: Pizzas Personalizadas

Cada estação espera uma informação e lembra as anteriores — como currying:

snippetJavaScript
const orderPizza = size => crust => topping =>  `${size} pizza de massa ${crust} com ${topping}`; orderPizza("Grande")("Fina")("Pepperoni"); const orderLargeThin = orderPizza("Grande")("Fina");orderLargeThin("Cogumelo");

Templates reutilizáveis (orderLargeThin) nascem de fechar argumentos cedo — closures em ação.

02

seção

O que é Currying?

Nomeado em homenagem a Haskell Curry. Converte função multi-arg em sequência de funções unárias.

snippetJavaScript
function add(a, b, c) { return a + b + c; }add(1, 2, 3);  // 6 const curriedAdd = a => b => c => a + b + c;curriedAdd(1)(2)(3);  // 6

Currying transforma a função — a lógica original só roda quando todos os argumentos foram fornecidos.

Passo a passo

snippetJavaScript
const add = a => b => c => a + b + c; const step1 = add(1);   // b => c => 1 + b + cconst step2 = step1(2); // c => 3 + cstep2(3);               // 6
snippetJavaScript
const multiply = a => b => a * b;const double = multiply(2);const triple = multiply(3); double(5);  // 10triple(5);  // 15

Veja Closures para o mecanismo por baixo.

03

seção

Implementando curry()

snippetJavaScript
function curry(fn) {  return function curried(...args) {    if (args.length >= fn.length) {      return fn.apply(this, args);    }    return (...nextArgs) => curried.apply(this, args.concat(nextArgs));  };}
snippetJavaScript
const curriedSum = curry((a, b, c) => a + b + c); curriedSum(1, 2, 3);   // 6curriedSum(1)(2)(3);   // 6curriedSum(1, 2)(3);   // 6 — misto OK

Versão ES6:

snippetJavaScript
const curry = fn =>  function curried(...args) {    return args.length >= fn.length      ? fn(...args)      : (...next) => curried(...args, ...next);  };

Limitação: fn.length ignora rest params e defaults:

snippetJavaScript
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }console.log(sum.length);  // 0 const curryN = (fn, arity) =>  function curried(...args) {    return args.length >= arity      ? fn(...args)      : (...next) => curried(...args, ...next);  };
04

seção

Currying vs Aplicação Parcial

AspectoCurryingAplicação parcial
Args por chamadaSempre umQualquer número
RetornoCadeia unáriaFunção com args fixos
NaturezaTransformação estruturalEspecialização
snippetJavaScript
// Curryingconst add = a => b => c => a + b + c;add(1)(2)(3); // Partialfunction partial(fn, ...preset) {  return (...later) => fn(...preset, ...later);}const greetExcitedly = partial(  (g, p, name) => `${g}, ${name}${p}`,  "Hello",  "!",);greetExcitedly("Alice");  // args restantes JUNTOS

Currying = flexibilidade máxima. Partial = versões pré-configuradas.

05

seção

Currying no Mundo Real

Logger

snippetJavaScript
const createLogger = level => withTimestamp => message => {  const time = withTimestamp ? new Date().toISOString() : "";  console.log(`[${level}]${time ? " " + time : ""} ${message}`);}; const info = createLogger("INFO")(true);info("Aplicação iniciada");

API client

snippetJavaScript
const createApiClient = baseUrl => endpoint => options =>  fetch(`${baseUrl}${endpoint}`, options).then(r => r.json()); const githubApi = createApiClient("https://api.github.com");const getUser = githubApi("/users");

Validadores

snippetJavaScript
const isGreaterThan = min => value => value > min;const isAdult = isGreaterThan(17);[15, 22, 45].filter(isAdult);  // [22, 45]

Desconto

snippetJavaScript
const applyDiscount = pct => price => price * (1 - pct / 100);const tenOff = applyDiscount(10);[100, 200, 50].map(tenOff);  // [90, 180, 45]
06

seção

Composição de Funções

Combinar funções: saída de uma → entrada da próxima. Notação matemática: (f ∘ g)(x) = f(g(x)).

snippetJavaScript
const add10 = x => x + 10;const multiply2 = x => x * 2;const subtract5 = x => x - 5; subtract5(multiply2(add10(5)));  // 25 const composed = compose(subtract5, multiply2, add10);composed(5);  // 25

Composição evita aninhamento ilegível:

snippetJavaScript
// ❌ addGreeting(capitalize(trim(getName(user))))// ✓const processUser = compose(addGreeting, capitalize, trim, getName);processUser(user);
07

seção

compose() e pipe()

FunçãoDireçãoLeitura
compose(f,g,h)Direita → esquerdaMatemática f(g(h(x)))
pipe(f,g,h)Esquerda → direitaOrdem de execução
snippetJavaScript
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
snippetJavaScript
const getName = obj => obj.name;const toUpperCase = str => str.toUpperCase();const addExclaim = str => str + "!"; pipe(getName, toUpperCase, addExclaim)({ name: "alice" });  // ALICE!compose(addExclaim, toUpperCase, getName)({ name: "alice" }); // idem

A maioria prefere pipe — funções listadas na ordem em que rodam.

08

seção

Pipelines de Dados

snippetJavaScript
const toCamelCase = pipe(  str => str.trim(),  str => str.toLowerCase(),  str => str.split(" "),  words => words.map((w, i) =>    i === 0 ? w : w[0].toUpperCase() + w.slice(1),  ),  words => words.join(""),); toCamelCase("  HELLO WORLD  ");  // helloWorld

API → UI

snippetJavaScript
const processApiResponse = pipe(  res => res.data,  users => users.filter(u => u.isActive),  users => users.sort((a, b) => a.name.localeCompare(b.name)),  users => users.map(u => ({    id: u.id,    displayName: `${u.firstName} ${u.lastName}`,    email: u.email,  })),  users => users.slice(0, 10),);

Combine com Map, Reduce & Filter quando data-last.

09

seção

Currying + Composição + Data-Last

Composição exige funções unárias (1 in → 1 out). Currying converte multi-arg nisso.

snippetJavaScript
// ❌ data-first — não compõeconst map = (arr, fn) => arr.map(fn); // ✓ data-lastconst map = fn => arr => arr.map(fn);const filter = fn => arr => arr.filter(fn); const doubleEvens = pipe(  filter(x => x % 2 === 0),  map(x => x * 2),); doubleEvens([1, 2, 3, 4, 5, 6]);  // [4, 8, 12]

Estilo point-free

Sem mencionar o dado explicitamente — foco nas transformações:

snippetJavaScript
const isPositive = x => x > 0;const double = x => x * 2;const sum = (a, b) => a + b; const processNumbers = pipe(  filter(isPositive),  map(double),  reduce(sum, 0),); processNumbers([1, -2, 3, -4, 5]);  // 18

Use point-free com moderação — clareza > esperteza.

10

seção

Lodash, Ramda e Vanilla

Bibliotecas trazem curry com placeholder, auto-currying e utilitários testados.

snippetJavaScript
// Ramda — data-last, auto-curried// R.pipe(R.prop("name"), R.trim, R.toLower)({ name: "  ALICE  " }) // lodash/fp// fp.pipe(fp.filter(u => u.age >= 18), fp.map(fp.get("name")))(users)

Vanilla para a maioria dos casos:

snippetJavaScript
const curry = fn => function curried(...args) {  return args.length >= fn.length ? fn(...args) : (...n) => curried(...args, ...n);};const pipe = (...fns) => x => fns.reduce((a, f) => f(a), x);const partial = (fn, ...preset) => (...rest) => fn(...preset, ...rest);const map = fn => arr => arr.map(fn);
SituaçãoRecomendação
Aprendizado / projeto pequenoVanilla
Já usa Lodashlodash/fp
FP pesadoRamda
Bundle pequenoVanilla ou tree-shake
11

seção

Erros Comuns

Currying: esquecer segunda chamada

snippetJavaScript
const add = a => b => a + b;add(1);  // [Function] — não é 3!add(1)(2);  // ✓

Data-first vs data-last

snippetJavaScript
const multiply = factor => value => value * factor;const double = multiply(2);pipe(double, triple)(5);  // ✓ com data-last

Composição: tipos incompatíveis

Saída de getAge (número) → getLength (espera array) quebra o pipeline.

Side effects no pipeline

Funções compostas devem ser puras.

Sobre-composição

Se o colega não entende o pipeline em 30s, prefira função explícita com optional chaining.

12

seção

Perguntas Clássicas de Entrevista

13

seção

Teste Seu Conhecimento

$ summary --quick

Resumo rápido

  • Currying — f(a,b,c) → f(a)(b)(c); closures lembram args
  • Currying ≠ partial — unário vs args fixos + resto junto
  • Composição — saída → entrada; pipelines legíveis
  • pipe — esquerda→direita; compose — direita→esquerda
  • Data-last — dados por último para compor map/filter/reduce curried
  • Point-free — transformações sem param explícito; use com moderação
  • curry() — fn.length + acumular args; cuidado com rest/default
  • Pipelines puros — side effects quebram previsibilidade
  • Vanilla basta na maioria; Ramda/lodash/fp para FP pesado
  • Trilha FP completa — base sólida para código declarativo em JS

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.