Una función anónima (o lambda) es un bloque de código sin nombre que puedes asignar a una variable o pasar como argumento a otra función. Si la lógica es sencilla y se reduce a una única expresión, puedes usar la sintaxis de flecha (=>), que es un azúcar sintáctico para { return expr; }. Si intentas meter más de una sentencia tras la flecha, el compilador te lanzará un error; para lógica compleja, necesitas el cuerpo con llaves {} y la palabra clave return.
Cuando una función anónima accede a variables que están fuera de su propio cuerpo, estamos ante un closure. Un closure no guarda una copia del valor de la variable en el momento de su creación, sino que mantiene una referencia a la variable misma dentro de su scope léxico. Esto significa que si la variable cambia después de que la función fue definida, la función verá el nuevo valor.
Debes usar tipos de función específicos, como int Function(int), en lugar del tipo genérico Function para aprovechar el sound type system de Dart; usar Function a secas hace que el compilador pierda la capacidad de verificar que los argumentos y el retorno sean correctos, lo que puede causar errores en tiempo de ejecución. Usa estas técnicas cuando necesites pasar comportamiento a métodos como map o where, o cuando necesites “recordar” un estado sin necesidad de envolver todo en una clase. Pero ten cuidado: si capturas una variable de un bucle, podrías terminar con todas tus funciones ejecutando el último valor del iterador si no manejas correctamente el scope de la variable.
void main() {
// Definición de tipos de función precisos para asegurar la seguridad de tipos
final int Function(int, int) sumar = (a, b) => a + b;
final void Function(String) saludar = (nombre) => print('Hola, $nombre');
// El closure 'aplicarFactor' captura la referencia a la variable 'factor'
int factor = 10;
final int Function(int) aplicarFactor = (n) => n * factor;
var numeros = [1, 2, 3, 4, 5];
// Uso de funciones anónimas con map (transformación) y where (filtrado)
// map usa una función de flecha por ser una sola expresión
var duplicados = numeros.map((n) => n * 2).toList();
// where usa una función anónima para evaluar un predicado
var pares = numeros.where((n) => n % 2 == 0).toList();
// Debido a que el closure captura la referencia, el cambio en 'factor'
// se refleja en el comportamiento de 'aplicarFactor'
factor = 2;
var multiplicados = numeros.map(aplicarFactor).toList();
print('Originales: $numeros');
print('Duplicados (map): $duplicados');
print('Pares (where): $pares');
print('Multiplicados con factor actualizado (2): $multiplicados');
// Demostración de la solución al problema de captura en bucles
final listaDeFunciones = <void Function()>[];
for (var i = 0; i < 3; i++) {
// Creamos una variable local en cada iteración para "congelar" el valor
// Esto crea un nuevo scope léxico para cada iteración del bucle
final currentI = i;
listaDeFunciones.add(() => print('Valor capturado: $currentI'));
}
print('Ejecutando callbacks del bucle:');
for (var callback in listaDeFunciones) {
callback();
}
}
Desglose del concepto
En el ejemplo, las variables sumar y saludar utilizan tipos de función explícitos (int Function(int, int) y void Function(String)). Esto permite que el compilador te avise si intentas pasar un String a sumar antes de que el programa falle en producción.
La función aplicarFactor es el ejemplo perfecto de un closure. No guarda el número 10 dentro de sí, sino que mantiene un enlace a la variable factor. Por eso, cuando modificamos factor = 2 más adelante, la función multiplicados devuelve valores basados en 2 y no en el valor original.
En las operaciones con numeros, las funciones anónimas dentro de map y where son pasadas como argumentos a métodos de orden superior. El método map aplica la transformación a cada elemento, mientras que where utiliza la lógica del predicado para decidir qué elementos permanecen en la secuencia.
Finalmente, en el bucle de listaDeFunciones, la clave es la declaración de final currentI = i;. Al declarar una variable dentro del cuerpo del for, estamos creando una nueva instancia de esa variable en cada iteración. Cada closure capturará una variable distinta (currentI), asegurando que cada callback tenga su propio valor independiente.
El error frecuente
Un error clásico ocurre cuando intentas capturar la variable de control del bucle directamente:
var callbacks = <void Function()>[];
for (var i = 0; i < 3; i++) {
// Error: todas las funciones capturan la misma referencia de 'i'
callbacks.add(() => print(i));
}
for (var cb in callbacks) {
cb(); // Imprimirá 3, 3, 3
}
En este caso, todas las funciones anónimas apuntan a la misma variable i. Cuando el bucle termina, el valor de i es 3, y como todas las funciones comparten esa misma referencia, todas imprimirán 3. Entender la distinción entre el valor actual y la referencia de la variable es vital para evitar este comportamiento inesperado.
N° 25