Swift 4.1: por que Apple renombró flatMap a compactMap

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 } // [1, 2, 3, 4, 5, 6, 7, 8, 9] 

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 } // 4 let b: Int? = nil let transformedB = b.flatMap { $0 * 2 } // nil 

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 } // [1, 2, 3, 4, 5, 6, 7] 

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 } // same as array.map { $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 .

Source: https://habr.com/ru/post/es414809/


All Articles