Swift 4.1: Warum Apple flatMap in compactMap umbenannt hat

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

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

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

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

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


All Articles