Saltar al contenido principal

Closures y Lambdas

Los closures son funciones que capturan variables de su ámbito circundante. Así es como diferentes lenguajes manejan closures, lambdas y funciones de orden superior.

Closures y Funciones 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)
}

Funciones de Orden 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

Puntos Clave​

  • Semántica de captura -- C++ usa [=] para captura por valor y [&] para captura por referencia; PHP usa use ($x) para captura por valor y use (&$x) para captura por referencia. Rust distingue Fn (préstamo inmutable), FnMut (préstamo mutable) y FnOnce (toma la propiedad). Por ejemplo, let mut count = 0; let inc = || { count += 1; count }; en Rust requiere FnMut porque el closure muta count. Elige captura explícita cuando necesites controlar el tiempo de vida y la mutabilidad; los traits de Rust imponen esto en tiempo de compilación y previenen movimientos accidentales o conflictos de préstamo.

  • Tipos de closure en Rust -- Los traits Fn, FnMut y FnOnce de Rust codifican la semántica de captura en tiempo de compilación. Un closure que solo lee variables capturadas implementa Fn; uno que las muta implementa FnMut; uno que las consume (e.g. move) implementa FnOnce. Al aceptar closures como parámetros, usa impl Fn(i32) -> i32 o F: Fn(i32) -> i32 para restringir lo que el llamador puede pasar. Esto importa porque FnOnce solo puede llamarse una vez, así que no puede usarse en un bucle. Si escribes closures que capturan estado, entiende qué trait implementa tu closure para evitar errores de propiedad.

  • Limitaciones de lambdas en C -- C no tiene closures ni lambdas; solo punteros a función (int (*op)(int, int)). Para simular "closures", debes pasar un struct de contexto manualmente: typedef struct { int factor; } Context; int apply(int x, Context* ctx) { return x * ctx->factor; }. Esto es verboso y propenso a errores. Para código con muchos callbacks en C, considera pasar un void* de contexto junto al puntero de función; para proyectos nuevos, prefiere lenguajes con closures nativos (Go, Rust, C++) si necesitas semántica de captura.

  • Effectively final en Java -- Las lambdas de Java solo pueden capturar variables que sean final o efectivamente final (nunca reasignadas). Por ejemplo, int factor = 3; Function<Integer, Integer> mul = x -> x * factor; funciona, pero factor = 4; después sería un error de compilación. Esto previene la mutación del estado capturado y simplifica el modelo pero limita los closures que necesitan mantener estado mutable. Para capturas inmutables o sin estado, las lambdas de Java están bien; para estado mutable, usa un arreglo de un elemento o AtomicInteger como solución alternativa, o prefiere un lenguaje con captura completa de closures (e.g. JavaScript, C#).

  • Funciones de orden superior y built-ins -- JavaScript, Python, Ruby, C# y Swift tienen map, filter, reduce (o equivalentes) incorporados. Go no los tiene; debes implementarlos o usar una librería. C usa qsort con un puntero a función comparadora. Rust usa métodos de iterador (.map(), .filter(), .fold()). Zig usa punteros a función y bucles manuales. Si dependes mucho de pipelines funcionales, elige un lenguaje con APIs ricas de iteradores/colecciones; para abstracción mínima, el estilo Go o C es aceptable.

  • Cuándo elegir cuál -- Para código web y orientado a eventos, los closures de JavaScript son idiomáticos. Para sistemas con seguridad y rendimiento, los closures de Rust con Fn/FnMut/FnOnce equilibran expresividad y control. Para Java empresarial, las lambdas y streams funcionan bien dentro de la restricción de "effectively final". Para C/C++/Zig, los punteros a función son la norma; usa closures solo cuando tengas C++ o Rust disponible. Ajusta el soporte de closures a tu caso de uso: muchos callbacks vs. predominantemente procedural vs. pipelines funcionales.