Swift 4.1: pourquoi Apple a renommé flatMap en compactMap

Bonjour, Habr!

Je m'appelle Alexander Zimin, je suis développeur iOS chez Badoo. Ceci est une traduction d'un article de mon collègue Schwib, dans lequel il décrit à quoi ressemblait la fonction flatMap dans Swift et pourquoi l'une de ses surcharges a été renommée compactMap. L'article est utile à la fois pour comprendre les processus se produisant dans le référentiel Swift et son évolution , ainsi que pour le développement général.



Dans la programmation fonctionnelle, il existe une définition claire de ce que flatMap fonction flatMap . La méthode flatMap prend une liste et une fonction de transformation (qui pour chaque transformation s'attend à obtenir zéro ou plusieurs valeurs), l'applique à chaque élément de la liste et crée une seule liste (aplatie). Ce comportement est différent de la fonction de map simple, qui applique une transformation à chaque valeur et s'attend à obtenir une seule valeur pour chaque transformation.



Pour plusieurs versions, Swift dispose d'une map et d'un flatMap . Cependant, dans Swift 4.1, vous ne pouvez plus appliquer flatMap à une séquence de valeurs et toujours passer une fermeture qui renvoie une valeur facultative. Il existe maintenant une méthode compactMap pour compactMap .

Au début, il n'est peut-être pas si facile de comprendre l'essence de l'innovation. Si flatMap fonctionnait bien, pourquoi introduire une méthode distincte? Voyons cela.

La bibliothèque standard Swift antérieure à la version 4.1 fournissait trois implémentations de surcharges pour 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] 

Passons en revue les trois options et voyons ce qu'elles font.

Sequence.flatMap <S> (_: (Element) -> S) -> [S.Element], où S: Sequence


La première surcharge concerne les séquences dans lesquelles une fermeture prend un élément de cette séquence et se convertit en une autre séquence.
flatMap toutes ces séquences transformées dans la séquence finale renvoyée comme résultat. Par exemple:

 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] 

Ceci est un excellent exemple du fonctionnement de la méthode flatMap . Nous allons transformer (mapper) chaque élément de la liste source et créer une nouvelle séquence. Grâce à flatMap résultat final est une structure aplatie de séquences transformées.

Optional.flatMap <U> (_: (Wrapped) -> U?) -> U?


La deuxième surcharge concerne les types facultatifs. Si le type facultatif que vous appelez a une valeur, la fermeture sera appelée avec la valeur sans le wrapper facultatif (valeur non encapsulée) et vous pouvez renvoyer la valeur facultative convertie.

 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> (_: (Element) -> U?) -> [U]


La troisième surcharge vous aidera à comprendre à quoi compactMap . Cette version ressemble à la première, mais il y a une différence importante. Dans ce cas, la fermeture revient facultative. flatMap traite, flatMap les valeurs nulles renvoyées et inclut tout le reste - dans le résultat en tant que valeurs sans wrapper.

 let array = [1, 2, 3, 4, nil, 5, 6, nil, 7] let arrayWithoutNils = array.flatMap { $0 } // [1, 2, 3, 4, 5, 6, 7] 

Mais dans ce cas, la commande n'est pas effectuée. Par conséquent, cette version de flatMap plus proche de la map que la définition purement fonctionnelle de flatMap . Et le problème avec cette surcharge est que vous ne pouvez pas l'utiliser correctement là où la map fonctionnerait parfaitement.

 let array = [1, 2, 3, 4, 5, 6] let transformed = array.flatMap { $0 } // same as array.map { $0 } 

Cette utilisation de flatMap correspond à la troisième surcharge, encapsulant implicitement la valeur convertie en option, puis supprimant l'encapsuleur à ajouter au résultat. La situation devient particulièrement intéressante si les conversions de chaînes ne sont pas utilisées correctement.

 struct Person { let name: String } let people = [Person(name: “Foo”), Person(name: “Bar”)] let names = array.flatMap { $0.name } 

Dans Swift avant la version 4.0, nous obtenions une conversion en [“Foo”, “Bar”] . Mais à partir de la version 4.0, les valeurs de chaîne implémentent le protocole de collecte. Par conséquent, notre utilisation de flatMap dans ce cas, au lieu de la troisième surcharge, correspondra à la première, et nous obtiendrons un résultat «aplati» à partir des valeurs converties: [“F”, “o”, “o”, “B”, “a”, “r”]

Lorsque vous appelez flatMap vous n'obtiendrez pas d'erreur, car cela est autorisé à l'utiliser. Mais la logique sera rompue, car le résultat est de type Array<Character>.Type , pas le Array<String>.Type .

Conclusion


Pour éviter l'utilisation abusive de flatMap , la troisième version surchargée a été supprimée de la nouvelle version Swift. Et pour résoudre le même problème (en supprimant les valeurs nulles), vous devez maintenant utiliser une méthode distincte - compactMap .

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


All Articles