Adaptadores fundamentales: map, filter, fold, sum y zip
Los cinco adaptadores que resuelven la mayoría de las transformaciones: `map` transforma cada elemento, `filter` selecciona algunos, `zip` empareja dos secuencias, y `fold` reduce todo a un valor acumulando —con `sum` como su caso especial más común—. La distinción entre adaptador perezoso y reductor que consume.
Con next y la pereza ya tienes la mecánica; ahora llega el vocabulario. Casi cualquier manipulación de una secuencia se expresa combinando cinco operaciones: map para transformar, filter para seleccionar, zip para emparejar, y fold para reducirlo todo a un único valor, del que sum es apenas el caso más frecuente. Aprenderlas no es memorizar una API: es adquirir una gramática con la que describir cálculos sobre colecciones sin escribir un solo bucle, y descubrir que bajo esa variedad late una sola operación —acumular— de la que las demás son variaciones.
- Transformar cada elemento con
mapy seleccionar confilter. - Reducir una secuencia a un valor con
foldy su acumulador. - Reconocer
sumyproductcomo casos especiales defold. - Emparejar dos iteradores con
zipy saber cuándo se detiene.
map: transformar cada elemento
map aplica una clausura a cada valor y produce un iterador con los resultados. Es la transformación uno a uno: la longitud no cambia, el tipo puede cambiar por completo.
let nombres = ["ada", "alan", "grace"];
let largos: Vec<usize> = nombres.iter().map(|s| s.len()).collect();
// [3, 4, 5]: de &str a usize
La clausura recibe cada Item y devuelve lo que quieras; map se limita a envolver esa promesa, perezosamente, sin ejecutarla hasta el consumo. Cambiar el tipo de salida es lo normal, no la excepción: de un texto a su longitud, de un registro a un campo, de un error a otro.
filter: quedarse con algunos
filter conserva solo los elementos que satisfacen un predicado. La secuencia se acorta; el tipo se mantiene.
let pares: Vec<i32> = (1..=10).filter(|n| n % 2 == 0).collect();
// [2, 4, 6, 8, 10]
Un matiz técnico revelador: el predicado recibe una referencia al elemento, &Self::Item. Por eso arriba n es &i32 y n % 2 funciona por deref automático. La razón es de propiedad: filter solo inspecciona para decidir, no consume, así que presta el valor en lugar de moverlo, y quien pase el filtro sigue su camino intacto.
fold, sum y product: el acumulador universal
fold es la operación de la que descienden casi todas las demás. Recibe un valor inicial y una clausura de dos argumentos —el acumulador y el elemento actual—, y recorre la secuencia entera fusionando cada elemento en el acumulador:
let suma = (1..=5).fold(0, |acc, x| acc + x); // 15
let producto = (1..=5).fold(1, |acc, x| acc * x); // 120
let concat = ["a", "b", "c"]
.iter()
.fold(String::new(), |mut s, x| { s.push_str(x); s }); // "abc"
Lee la firma mental: partes de init, y por cada elemento sustituyes el acumulador por el resultado de la clausura. Al agotarse la secuencia, el acumulador final es el resultado. fold no es perezoso: es un consumidor que recorre todo y devuelve un valor. Y fíjate en el poder: con solo cambiar el inicial y la clausura, fold suma, multiplica, concatena, cuenta o construye cualquier estructura.
Sumar y multiplicar son tan comunes que tienen consumidores propios, sum y product, más legibles que el fold equivalente:
let total: i32 = (1..=100).sum(); // 5050
let fact: u64 = (1..=10).product(); // 3628800
Necesitan una anotación de tipo (: i32) porque sum es genérico sobre a qué tipo sumar. Conceptualmente, sum() es fold(0, |a, b| a + b) y product() es fold(1, |a, b| a * b): el mismo motor de acumulación, con nombre propio.
zip: caminar dos a la vez
zip entrelaza dos iteradores en uno de pares, avanzando ambos en paralelo y produciendo tuplas (a, b). Se detiene en cuanto el más corto se agota:
let nombres = ["Ada", "Alan", "Grace"];
let edades = [36, 41, 45];
let fichas: Vec<(&str, i32)> = nombres
.iter()
.copied()
.zip(edades.iter().copied())
.collect();
// [("Ada", 36), ("Alan", 41), ("Grace", 45)]
Como los adaptadores se componen, zip brilla junto a map: emparejar y luego combinar cada par es un patrón diario, por ejemplo para el producto escalar de dos vectores.
let a = [1, 2, 3];
let b = [4, 5, 6];
let punto: i32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
// 1*4 + 2*5 + 3*6 = 32
map · adaptador
Transforma cada elemento uno a uno. Perezoso: devuelve un iterador. La longitud se conserva, el tipo puede cambiar.
filter · adaptador
Conserva los que cumplen un predicado que recibe &Item. Perezoso. La longitud se acorta, el tipo se mantiene.
zip · adaptador
Empareja dos iteradores en tuplas y se detiene con el más corto. Perezoso.
fold · consumidor
Reduce toda la secuencia a un valor acumulando. Ejecuta. sum y product son casos suyos.
flowchart LR src[Rango 1 a 5] --> mapa[map duplica cada valor] mapa --> filtro[filter deja los mayores que cuatro] filtro --> fold[fold acumula en un total] fold --> res[Un unico valor final] style src fill:#a6e3a1,color:#11111b style fold fill:#89b4fa,color:#11111b style res fill:#cba6f7,color:#11111b
Mira fold de cerca, porque estás ante la operación primitiva de todo procesamiento de secuencias. Su forma —parte de un valor inicial y fusiona cada elemento en un acumulador— es lo que la teoría llama un catamorfismo: la manera canónica de colapsar una estructura en un resultado. Y su generalidad no es retórica. sum es fold con inicial 0 y la suma; product, con 1 y la multiplicación; count es fold(0, |c, _| c + 1); last, max y min son fold que van reteniendo un candidato; hasta collect puede escribirse como un fold que empieza en una colección vacía y empuja cada elemento. Bajo una docena de consumidores distintos late el mismo esqueleto: recorrer una vez, llevando un estado que se actualiza con cada valor. Esa unidad es profunda porque revela la anatomía de la iteración misma: iterar es doblar una secuencia sobre un acumulador, y la clausura que le pasas es el álgebra con la que se dobla —qué significa combinar el resultado acumulado con el siguiente elemento—. Cambia el álgebra y cambias el sentido de la operación sin tocar el esqueleto. Por eso dominar fold no es aprender un método más de una lista larga: es entender la operación de la que la lista larga es solo un catálogo de casos con nombre. Cuando ningún consumidor prefabricado encaja con lo que necesitas, casi siempre la respuesta es un fold con la clausura adecuada; y cuando uno sí encaja, conviene saber que por dentro es un fold. La programación funcional entera puede leerse como el arte de elegir el álgebra correcta para doblar una estructura, y aquí, en cinco letras, está su corazón.
De estos cinco, tres son adaptadores perezosos —map, filter, zip— y devuelven iteradores que puedes seguir encadenando; dos son reductores que consumen —fold y su caso sum— y devuelven un valor final, cerrando la cadena. Esta línea divisoria, que ya viste en la lección de la pereza, es la que gobierna el próximo tema: los consumidores. Un buen olfato para saber si un método “sigue construyendo” o “ya ejecuta” vale más que memorizar la firma de cada uno.
- Con
map, convierte["1", "2", "3"]en unVec<i32>parseando cada cadena, y encadena un.sum::<i32>()al final. - Con
filter, quédate con los múltiplos de 3 en1..=30y cuéntalos con.count(). Explica por qué el predicado recibe&i32y noi32. - Escribe con
folduna expresión que calcule el máximo de[3, 7, 2, 9, 4]sin usar el métodomax; define tú el acumulador inicial. - Usa
zippara emparejar["lun", "mar", "mie"]con[1, 2, 3]y construye unVecde tuplas. - Reescribe
(1..=5).sum::<i32>()como unfoldy(1..=5).product::<i32>()como otro, y comprueba conassert_eq!que dan lo mismo.