Hallo Habr!
Mein Name ist Alexander Zimin, ich bin ein iOS-Entwickler bei Badoo. Dies ist eine Übersetzung eines Artikels meines Kollegen Schwib, in dem er beschrieb, wie die flatMap-Funktion in Swift war und warum eine ihrer Überladungen in compactMap umbenannt wurde. Der Artikel ist sowohl zum Verständnis der im
Swift-Repository und
seiner Entwicklung ablaufenden Prozesse als auch für die allgemeine Entwicklung hilfreich.

In der funktionalen Programmierung gibt es eine klare Definition der
flatMap
Funktion. Die
flatMap
Methode verwendet eine Liste und eine Transformationsfunktion (die für jede Transformation null oder mehr Werte erwartet), wendet sie auf jedes Element der Liste an und erstellt eine einzelne (abgeflachte) Liste. Dieses Verhalten unterscheidet sich von der einfachen
map
, die eine Transformation auf jeden Wert anwendet und erwartet, dass für jede Transformation nur ein Wert erhalten wird.

Für mehrere Versionen verfügt Swift über eine
map
und eine
flatMap
. In
Swift 4.1 können Sie
flatMap
jedoch nicht mehr auf eine Folge von Werten anwenden und dennoch einen Abschluss übergeben, der einen optionalen Wert zurückgibt.
compactMap
gibt es jetzt eine
compactMap
Methode.
Zunächst ist es möglicherweise nicht so einfach, das Wesentliche der Innovation zu verstehen. Wenn
flatMap
gut funktioniert hat, warum eine separate Methode einführen? Lass es uns herausfinden.
Die Swift-Standardbibliothek vor Version 4.1 bot drei Implementierungen von Überladungen für
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]
Lassen Sie uns alle drei Optionen durchgehen und sehen, was sie tun.
Sequence.flatMap <S> (_: (Element) -> S) -> [S.Element], wobei S: Sequence
Die erste Überladung betrifft Sequenzen, bei denen ein Abschluss ein Element dieser Sequenz nimmt und in eine andere Sequenz konvertiert.
flatMap
alle diese transformierten Sequenzen in die endgültige Sequenz, die als Ergebnis zurückgegeben wird. Zum Beispiel:
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] let flattened = array.flatMap { $0 }
Dies ist ein großartiges Beispiel dafür, wie die
flatMap
Methode funktionieren sollte. Wir werden jedes Element der Quellliste transformieren (zuordnen) und eine neue Sequenz erstellen. Dank
flatMap
Endergebnis eine abgeflachte Struktur transformierter Sequenzen.
Optional.flatMap <U> (_: (Wrapped) -> U?) -> U?
Die zweite Überlastung betrifft optionale Typen. Wenn der aufgerufene optionale Typ einen Wert hat, wird der Abschluss mit dem Wert ohne den optionalen Wrapper (nicht umschlossener Wert) aufgerufen, und Sie können den konvertierten optionalen Wert zurückgeben.
let a: Int? = 2 let transformedA = a.flatMap { $0 * 2 }
Sequence.flatMap <U> (_: (Element) -> U?) -> [U]
Die dritte Überlastung hilft Ihnen zu verstehen, wofür
compactMap
. Diese Version sieht genauso aus wie die erste, aber es gibt einen wichtigen Unterschied. In diesem Fall wird der Verschluss optional zurückgegeben.
flatMap
verarbeitet es, überspringt die zurückgegebenen
flatMap
und schließt den Rest ein - im Ergebnis als Werte ohne Wrapper.
let array = [1, 2, 3, 4, nil, 5, 6, nil, 7] let arrayWithoutNils = array.flatMap { $0 }
In diesem Fall wird die Bestellung jedoch nicht ausgeführt. Daher ist diese Version von
flatMap
näher an der
map
als die rein funktionale Definition von
flatMap
. Und das Problem mit dieser Überlastung ist, dass Sie sie möglicherweise nicht richtig verwenden, wenn die
map
perfekt funktionieren würde.
let array = [1, 2, 3, 4, 5, 6] let transformed = array.flatMap { $0 }
Diese Verwendung von
flatMap
entspricht der dritten Überladung, bei der der konvertierte Wert implizit in optional eingeschlossen und dann der Wrapper entfernt wird, um das Ergebnis zu ergänzen. Die Situation wird besonders interessant, wenn String-Konvertierungen nicht korrekt verwendet werden.
struct Person { let name: String } let people = [Person(name: “Foo”), Person(name: “Bar”)] let names = array.flatMap { $0.name }
In Swift vor Version 4.0 würden wir eine Konvertierung in
[“Foo”, “Bar”]
. Ab Version 4.0 implementieren Zeichenfolgenwerte jedoch das Collection-Protokoll. Daher entspricht unsere Verwendung von
flatMap
in diesem Fall anstelle der dritten Überladung der ersten, und wir erhalten ein "abgeflachtes" Ergebnis aus den konvertierten Werten:
[“F”, “o”, “o”, “B”, “a”, “r”]
Beim Aufruf von
flatMap
wird keine Fehlermeldung
flatMap
da dies zulässig ist. Die Logik wird jedoch unterbrochen, da das Ergebnis vom Typ
Array<Character>.Type
und nicht vom erwarteten
Array<String>.Type
.
Fazit
Um den Missbrauch von
flatMap
zu vermeiden, wurde die dritte überladene Version aus der neuen Swift-Version entfernt. Und um das gleiche Problem zu lösen (
compactMap
entfernen), müssen Sie jetzt eine separate Methode verwenden -
compactMap
.