Hola Habr!
Mi nombre es Alexander Zimin, soy desarrollador de iOS en Badoo. Esta es una traducción de un artículo de mi colega Schwib, en el que describió cómo era la función flatMap en Swift y por qué una de sus sobrecargas pasó a llamarse compactMap. El artículo es útil tanto para comprender los procesos que ocurren en
el repositorio Swift y
su evolución , como para el desarrollo general.

En la programación funcional, hay una definición clara de lo que
flatMap
función
flatMap
. El método
flatMap
toma una lista y una función de transformación (que para cada transformación espera obtener cero o más valores), la aplica a cada elemento de la lista y crea una lista única (aplanada). Este comportamiento es diferente de la función de
map
simple, que aplica una transformación a cada valor y espera obtener solo un valor para cada transformación.

Para varias versiones, Swift tiene un
map
y
flatMap
. Sin embargo, en
Swift 4.1 ya no puede aplicar
flatMap
a una secuencia de valores y aún pasar un cierre que devuelve un valor opcional. Ahora hay un método
compactMap
para
compactMap
.
Al principio puede no ser tan fácil entender la esencia de la innovación. Si
flatMap
funcionó bien, ¿por qué introducir un método separado? Vamos a resolverlo.
La biblioteca estándar de Swift anterior a la versión 4.1 proporcionaba tres implementaciones de sobrecargas para
flatMap
:
1. Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element], S : Sequence 2. Optional.flatMap<U>(_: (Wrapped) -> U?) -> U? 3. Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
Veamos las tres opciones y veamos qué hacen.
Sequence.flatMap <S> (_: (Elemento) -> S) -> [S.Element], donde S: Secuencia
La primera sobrecarga es para secuencias en las que un cierre toma un elemento de esa secuencia y se convierte en otra secuencia.
flatMap
todas estas secuencias transformadas en la secuencia final devuelta como resultado. Por ejemplo:
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] let flattened = array.flatMap { $0 }
Este es un gran ejemplo de cómo debería funcionar el método
flatMap
. Transformaremos (mapearemos) cada elemento de la lista fuente y crearemos una nueva secuencia. Gracias a
flatMap
resultado final es una estructura aplanada de secuencias transformadas.
Opcional.flatMap <U> (_: (Envuelto) -> U?) -> U?
La segunda sobrecarga es para tipos opcionales. Si el tipo opcional que está llamando tiene un valor, se llamará al cierre con el valor sin el contenedor opcional (valor sin envolver), y puede devolver el valor opcional convertido.
let a: Int? = 2 let transformedA = a.flatMap { $0 * 2 }
Sequence.flatMap <U> (_: (Elemento) -> U?) -> [U]
La tercera sobrecarga lo ayudará a comprender para qué
compactMap
. Esta versión se ve igual que la primera, pero hay una diferencia importante. En este caso, el cierre vuelve opcional.
flatMap
procesa, omitiendo los valores nulos devueltos e incluye todo el resto, en el resultado como valores sin un contenedor.
let array = [1, 2, 3, 4, nil, 5, 6, nil, 7] let arrayWithoutNils = array.flatMap { $0 }
Pero en este caso, no se realiza el pedido. Por lo tanto, esta versión de
flatMap
más cerca del
map
que la definición puramente funcional de
flatMap
. Y el problema con esta sobrecarga es que no puede usarlo correctamente donde el
map
funcionaría perfectamente.
let array = [1, 2, 3, 4, 5, 6] let transformed = array.flatMap { $0 }
Este uso de
flatMap
corresponde a la tercera sobrecarga, envolviendo implícitamente el valor convertido en opcional, y luego eliminando el contenedor para agregarlo al resultado. La situación se vuelve especialmente interesante si las conversiones de cadenas no se usan correctamente.
struct Person { let name: String } let people = [Person(name: “Foo”), Person(name: “Bar”)] let names = array.flatMap { $0.name }
En Swift antes de la versión 4.0, obtendríamos una conversión a
[“Foo”, “Bar”]
. Pero a partir de la versión 4.0, los valores de cadena implementan el protocolo Collection. Por lo tanto, nuestro uso de
flatMap
en este caso, en lugar de la tercera sobrecarga, corresponderá a la primera, y obtendremos un resultado "aplanado" de los valores convertidos:
[“F”, “o”, “o”, “B”, “a”, “r”]
Al llamar a
flatMap
no recibirá un error, ya que se permite su uso. Pero la lógica se romperá, porque el resultado es de tipo
Array<Character>.Type
, no el
Array<String>.Type
.
Conclusión
Para evitar el mal uso de
flatMap
, la tercera versión sobrecargada se ha eliminado de la nueva versión de Swift. Y para resolver el mismo problema (eliminar valores nulos) ahora necesita usar un método separado:
compactMap
.