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 }
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 }
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 }
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 }
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
.