Pular para o conteúdo principal

Closures & Lambdas

Closures são funções que capturam variáveis do escopo ao redor. Veja como diferentes linguagens lidam com closures, lambdas e funções de ordem superior.

Closures & Funções Anônimas​

// Arrow functions (closures)
const add = (a, b) => a + b;
const square = x => x * x;

// Capturing variables
function counter() {
let count = 0;
return {
increment: () => ++count,
getCount: () => count,
};
}
const c = counter();
c.increment(); // 1
c.increment(); // 2
c.getCount(); // 2

// IIFE (Immediately Invoked)
const result = ((x) => x * 2)(5); // 10

// Closures in loops
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
// Prints 0, 1, 2 (let creates new scope)
}

Funções de Ordem Superior​

// Map, filter, reduce
const nums = [1, 2, 3, 4, 5];

nums.map(x => x * 2); // [2, 4, 6, 8, 10]
nums.filter(x => x % 2 === 0); // [2, 4]
nums.reduce((acc, x) => acc + x, 0); // 15

// Function composition
const compose = (f, g) => x => f(g(x));
const double = x => x * 2;
const addOne = x => x + 1;
const doubleThenAdd = compose(addOne, double);
doubleThenAdd(3); // 7

// Currying
const curry = fn => a => b => fn(a, b);
const add = curry((a, b) => a + b);
const add5 = add(5);
add5(3); // 8

Principais Conclusões​

  • Semântica de captura -- C++ usa [=] para captura por valor e [&] para por referência; PHP usa use ($x) para por valor e use (&$x) para por referência. Rust distingue Fn (empréstimo imutável), FnMut (empréstimo mutável) e FnOnce (toma posse). Por exemplo, let mut count = 0; let inc = || { count += 1; count }; em Rust requer FnMut porque a closure muta count. Escolha captura explícita quando precisar controlar tempo de vida e mutabilidade; os traits de Rust impõem isso em tempo de compilação e previnem movimentos acidentais ou conflitos de empréstimo.

  • Tipos de closure em Rust -- Os traits Fn, FnMut e FnOnce de Rust codificam a semântica de captura em tempo de compilação. Uma closure que apenas lê variáveis capturadas implementa Fn; uma que as muta implementa FnMut; uma que as consome (ex: move) implementa FnOnce. Ao aceitar closures como parâmetros, use impl Fn(i32) -> i32 ou F: Fn(i32) -> i32 para restringir o que o chamador pode passar. Isso importa porque FnOnce só pode ser chamada uma vez, então não pode ser usada em um loop. Se você escreve closures que capturam estado, entenda qual trait sua closure implementa para evitar erros de ownership.

  • Limitações de lambda em C -- C não tem closures ou lambdas; apenas ponteiros de função (int (*op)(int, int)). Para simular "closures", você deve passar uma struct de contexto manualmente: typedef struct { int factor; } Context; int apply(int x, Context* ctx) { return x * ctx->factor; }. Isso é verboso e propenso a erros. Para código com muitos callbacks em C, considere passar um contexto void* junto com o ponteiro de função; para novos projetos, prefira linguagens com closures nativas (Go, Rust, C++) se precisar de semântica de captura.

  • Effectively final em Java -- Lambdas em Java só podem capturar variáveis que são final ou effectively final (nunca reatribuídas). Por exemplo, int factor = 3; Function<Integer, Integer> mul = x -> x * factor; funciona, mas factor = 4; depois seria um erro de compilação. Isso previne mutação de estado capturado e simplifica o modelo, mas limita closures que precisam manter estado mutável. Para capturas imutáveis ou sem estado, lambdas de Java são suficientes; para estado mutável, use um array de um elemento ou AtomicInteger como alternativa, ou prefira uma linguagem com captura completa de closures (ex: JavaScript, C#).

  • Funções de ordem superior e built-ins -- JavaScript, Python, Ruby, C# e Swift têm map, filter, reduce (ou equivalentes) built-in. Go não tem; você deve implementá-los ou usar uma biblioteca. C usa qsort com um ponteiro de função comparador. Rust usa métodos de iterador (.map(), .filter(), .fold()). Zig usa ponteiros de função e loops manuais. Se você depende muito de pipelines funcionais, escolha uma linguagem com APIs ricas de iterador/coleção; caso contrário, espere código imperativo mais verboso.

  • Quando escolher qual -- Para código web e orientado a eventos, closures de JavaScript são idiomáticas. Para sistemas com segurança e performance, closures de Rust com Fn/FnMut/FnOnce equilibram expressividade e controle. Para Java empresarial, lambdas e streams funcionam bem dentro da restrição "effectively final". Para C/C++/Zig, ponteiros de função são a norma; use closures apenas quando tiver C++ ou Rust disponível. Combine o suporte a closures com seu caso de uso: pesado em callbacks vs. predominantemente procedural vs. pipelines funcionais.