
Ich möchte Anfängern das Konzept der
funktionalen Programmierung auf einfachste Weise vorstellen und einige seiner Vorteile gegenüber den vielen anderen hervorheben, die den Code wirklich lesbarer und ausdrucksvoller machen. Ich habe einige interessante Demos für Sie auf dem
Playground
auf
Github aufgenommen .
Funktionsprogrammierung: Definition
Erstens ist die
funktionale Programmierung keine Sprache oder Syntax, sondern höchstwahrscheinlich eine Möglichkeit, Probleme zu lösen, indem komplexe Prozesse in einfachere und ihre nachfolgende Zusammensetzung zerlegt werden. Wie der Name schon sagt, "
Functional Programming ", ist die Kompositionseinheit für diesen Ansatz eine
Funktion ; und der Zweck einer solchen
Funktion besteht darin, zu vermeiden, dass sich der Zustand oder die Werte außerhalb ihres
scope)
ändern.
In
Swift
World gibt es dafür alle Bedingungen, da die
Funktionen hier ebenso voll am Programmierprozess beteiligt sind wie
Objekte, und das Problem der
mutation
auf der Ebene des
value
TYPES (Strukturstrukturen und Enumerationen) gelöst wird, die bei der Verwaltung der Mutabilität (
mutation
helfen. ) und klar kommunizieren, wie und wann dies geschehen kann.
Swift
nicht im vollen Sinne die Sprache der
funktionalen Programmierung , sondern zwingt Sie nicht zur
funktionalen Programmierung , obwohl es die Vorteile
funktionaler Ansätze erkennt und Wege findet, sie einzubetten.
In diesem Artikel konzentrieren wir uns darauf, die in
Swift
Elemente der
funktionalen Programmierung zu verwenden (dh "out of the box") und zu verstehen, wie Sie sie bequem in Ihrer Anwendung verwenden können.
Imperative und funktionale Ansätze: Vergleich
Um den
funktionalen Ansatz zu bewerten, vergleichen wir die Lösungen auf zwei verschiedene Arten mit einem einfachen Problem. Die erste Lösung ist "
Imperativ ", bei der der Code den Status innerhalb des Programms ändert.
Beachten Sie, dass wir die Werte innerhalb des veränderlichen Arrays mit dem Namen
numbers
bearbeiten und dann auf der Konsole drucken. Versuchen Sie anhand dieses Codes, die folgenden Fragen zu beantworten, die wir in naher Zukunft diskutieren werden:
- Was versuchen Sie mit Ihrem Code zu erreichen?
- Was passiert, wenn ein anderer
thread
versucht, auf das numbers
zuzugreifen, während Ihr Code ausgeführt wird? - Was passiert, wenn Sie Zugriff auf die ursprünglichen Werte im
numbers
haben möchten? - Wie zuverlässig kann dieser Code getestet werden?
Schauen wir uns nun einen alternativen "
funktionalen " Ansatz an:
In diesem Code erhalten wir das gleiche Ergebnis auf der Konsole und nähern uns der Lösung des Problems auf völlig andere Weise. Beachten Sie, dass unser
numbers
diesmal dank des Schlüsselworts
let
unveränderlich ist. Wir haben den Prozess des Multiplizierens von Zahlen aus dem
numbers
in die
timesTen()
-Methode
timesTen()
, die sich in der
extension
Array
. Wir verwenden immer noch eine
for
Schleife und ändern eine Variable namens
output
, aber der
scope
dieser Variablen wird nur durch diese Methode begrenzt. In ähnlicher Weise wird unser Eingabeargument
self
als Wert (
by value
) an die
timesTen()
-Methode übergeben und hat denselben Gültigkeitsbereich wie die Ausgabe der
output
. Die
timesTen()
-Methode wird aufgerufen, und wir können sowohl das ursprüngliche
numbers
als auch das Ergebnisarray-
result
auf der Konsole drucken.
Kommen wir zurück zu unseren 4 Fragen.
1. Was versuchst du mit deinem Code zu erreichen?In unserem Beispiel führen wir eine sehr einfache Aufgabe aus, indem wir die Zahlen im
numbers
mit
10
multiplizieren.
Bei einem
imperativen Ansatz müssen Sie, um eine Ausgabe zu erhalten, wie ein Computer denken und den Anweisungen in der
for
Schleife folgen. In diesem Fall zeigt der Code, wie Sie das Ergebnis erzielen. Beim
funktionalen Ansatz wird "
" in die
timesTen()
-Methode "eingeschlossen". Vorausgesetzt, diese Methode wurde an anderer Stelle implementiert, können Sie wirklich nur den Ausdruck
numbers.timesTen()
. Ein solcher Code zeigt deutlich,
durch diesen Code erreicht wird und nicht, wie die Aufgabe gelöst wird. Dies wird als
deklarative Programmierung bezeichnet , und es ist leicht zu erraten, warum ein solcher Ansatz attraktiv ist.
Der zwingende Ansatz lässt den Entwickler verstehen,
Code funktioniert, um zu bestimmen,
er tun soll.
Der funktionale Ansatz im Vergleich zum
imperativen Ansatz ist viel „ausdrucksvoller“ und bietet dem Entwickler die luxuriöse Möglichkeit, einfach anzunehmen, dass die Methode das tut, was sie behauptet zu tun! (Diese Annahme gilt natürlich nur für vorverifizierten Code).
2. Was passiert, wenn ein anderer thread
versucht, auf das numbers
zuzugreifen, während Ihr Code ausgeführt wird?Die oben dargestellten Beispiele existieren in einem vollständig isolierten Raum, obwohl es in einer komplexen Umgebung mit mehreren Threads durchaus möglich ist, dass zwei
threads
versuchen, auf dieselben Ressourcen zuzugreifen. Im Fall des
imperativen Ansatzes ist leicht zu erkennen, dass das Ergebnis durch die Reihenfolge bestimmt wird, in der die
threads
auf das
numbers
zugreifen, wenn ein anderer
thread
während der Verwendung Zugriff auf das
numbers
. Diese Situation wird als
race condition
und kann zu unvorhersehbarem Verhalten und sogar zu Instabilität und Absturz der Anwendung führen.
Im Vergleich dazu hat der
funktionale Ansatz keine „Nebenwirkungen“. Mit anderen Worten, die Ausgabe der
output
ändert keine in unserem System gespeicherten Werte und wird ausschließlich durch die Eingabe bestimmt. In diesem Fall erhält jeder Thread (
threads
), der Zugriff auf das
numbers
, IMMER dieselben Werte und sein Verhalten ist stabil und vorhersehbar.
3. Was passiert, wenn Sie Zugriff auf die im
numbers
gespeicherten Originalwerte haben möchten?
Dies ist eine Fortsetzung unserer Diskussion über „Nebenwirkungen“. Offensichtlich werden Statusänderungen nicht verfolgt. Daher verlieren wir mit dem
imperativen Ansatz den Anfangszustand unseres
numbers
während des Konvertierungsprozesses. Unsere auf dem
funktionalen Ansatz basierende Lösung speichert das ursprüngliche
numbers
und generiert ein neues
result
mit den gewünschten Eigenschaften am Ausgang. Das ursprüngliche
numbers
bleibt erhalten und ist für die zukünftige Verarbeitung geeignet.
4. Wie zuverlässig kann dieser Code getestet werden?
Da der
funktionale Ansatz alle „Nebenwirkungen“ zerstört, liegt die getestete Funktionalität vollständig innerhalb der Methode. Bei der Eingabe dieser Methode werden NIEMALS Änderungen vorgenommen, sodass Sie den Zyklus mehrmals testen können, so oft Sie möchten, und IMMER das gleiche Ergebnis erhalten. In diesem Fall ist das Testen sehr einfach. Im Vergleich dazu ändert das Testen der
Imperative- Lösung in einer Schleife den Beginn des Eintrags und Sie erhalten nach jeder Iteration völlig andere Ergebnisse.
Zusammenfassung der Vorteile
Wie wir anhand eines sehr einfachen Beispiels gesehen haben, ist der
funktionale Ansatz eine coole Sache, wenn Sie sich mit einem Datenmodell befassen, weil:
- Es ist deklarativ
- Es behebt Thread-bezogene Probleme wie
race condition
und Deadlocks - Der Zustand bleibt unverändert, der für nachfolgende Transformationen verwendet werden kann.
- Es ist leicht zu testen.
Lassen Sie uns etwas weiter gehen, um
funktionale Programmierung in
Swift
lernen. Es wird davon ausgegangen, dass die Hauptakteure Funktionen sind und in erster Linie
Objekte der ersten Klasse sein sollten .
First Class-Funktionen und Funktionen höherer Ordnung
Damit eine Funktion erstklassig ist, muss sie als Variable deklariert werden können. Auf diese Weise können Sie die Funktion als normalen Datentyp verwalten und gleichzeitig ausführen. Glücklicherweise sind Funktionen in
Swift
Objekte der ersten Klasse, dh sie werden unterstützt, indem sie als Argumente an andere Funktionen übergeben, als Ergebnis anderer Funktionen zurückgegeben, Variablen zugewiesen oder in Datenstrukturen gespeichert werden.
Aus diesem Grund haben wir in
Swift
andere Funktionen - Funktionen höherer Ordnung, die als Funktionen definiert sind, die eine andere Funktion als Argument verwenden oder eine Funktion zurückgeben. Es gibt viele davon:
map
,
filter
,
flatMap
,
compactMap
,
flatMap
,
compactMap
,
compactMap
usw. Die häufigsten Beispiele für Funktionen höherer Ordnung sind
map
,
filter
und
reduce
. Sie sind nicht global, sie sind alle an bestimmte Typen gebunden. Sie arbeiten mit allen Sequenztypen, einschließlich der
Collection
, die durch
Swift
Datenstrukturen wie ein
Array
, ein
Dictionary
und einen
Set
. In
Swift 5
funktionieren Funktionen höherer Ordnung auch mit einem völlig neuen TYPE -
Result
.
map(_:)
In
Swift
map(_:)
eine Funktion als Parameter und konvertiert die Werte eines bestimmten
gemäß dieser Funktion. Wenn Sie beispielsweise
map(_:)
auf ein Array von
Array
Werten anwenden, wenden wir eine Parameterfunktion auf jedes Element des ursprünglichen Arrays an und erhalten ein Array von
Array
, aber auch die konvertierten Werte.
Im obigen Code haben wir die Funktion
timesTen (_:Int)
, die einen ganzzahligen
Int
Wert annimmt und den ganzzahligen Wert
Int
multipliziert mit
10
zurückgibt, und ihn als Eingabeparameter für unsere
map(_:)
Funktion höherer Ordnung
map(_:)
verwendet und auf unser Array angewendet
numbers
. Wir haben das Ergebnis, das wir brauchen, im
result
.
Der Name der Parameterfunktion
timesTen
für Funktionen höherer Ordnung wie
map(_:)
spielt keine Rolle, der
Eingabeparameters und der Rückgabewert sind wichtig,
timesTen
die Signatur
(Int) -> Int
Funktionseingabeparameters. Daher können wir anonyme Funktionen in
map(_:)
- Schließungen - in jeder Form verwenden, einschließlich solcher mit verkürzten Argumentnamen
$0
,
$1
usw.
Wenn wir uns die Funktion
map(_ :)
für ein
Array
ansehen, könnte dies folgendermaßen aussehen:
func map<T>(_ transform: (Element) -> T) -> [T] { var returnValue = [T]() for item in self { returnValue.append(transform(item)) } return returnValue }
Dies ist ein zwingender Code, der uns vertraut ist, aber es ist kein Entwicklerproblem mehr, es ist ein
Apple
Problem, ein
Swift
Problem. Die Implementierung der
map(_:)
-Funktion höherer Ordnung wird von
Apple
hinsichtlich der Leistung optimiert, und uns Entwicklern wird die
map(_:)
-Funktionalität garantiert, sodass wir nur mit der
transform
wir wollen, korrekt ausdrücken können, ohne uns Sorgen machen zu müssen
es umgesetzt wird. Als Ergebnis erhalten wir perfekt lesbaren Code in Form einer einzelnen Zeile, der besser und schneller funktioniert.
Der von der Parameterfunktion zurückgegebene
stimmt möglicherweise nicht mit dem
Elemente in der ursprünglichen Sammlung überein.
Im obigen Code haben wir mögliche Ganzzahlen mit möglichen Zahlen, die als Zeichenfolgen dargestellt werden, und wir möchten sie in Ganzzahlen von
Int
konvertieren, indem
failable
den
failable
Initialisierer
Int(_ :String)
failable
Int(_ :String)
der durch den Abschluss
{ str in Int(str) }
. Wir tun dies mit
map(_:)
und erhalten ein
mapped
Array von
Optional
als Ausgabe:

Wir konnten
Elemente unseres Arrays mit
possibleNumbers
Zahlen in Ganzzahlen konvertieren. Daher erhielt ein Teil den Wert
nil
, was darauf hinweist, dass es unmöglich ist, die
String
in eine Ganzzahl
Int
konvertieren, und der andere Teil wurde in
Optionals
, die Werte haben:
print (mapped)
compactMap(_ :)
Wenn die an die Funktion höherer Ordnung übergebene Parameterfunktion am Ausgang einen
Optional
Wert hat, kann es sinnvoller sein, eine andere Funktion höherer Ordnung zu verwenden, deren Bedeutung ähnlich ist -
compactMap(_ :)
, die dasselbe tut wie
map(_:)
, erweitert aber zusätzlich die am
Optional
Ausgang empfangenen Werte und entfernt
nil
Werte aus der Sammlung.

In diesem Fall erhalten wir ein Array von
compactMapped
TYPE
[Int]
, aber möglicherweise kleiner:
let possibleNumbers = ["1", "2", "three", "///4///", "5"] let compactMapped = possibleNumbers.compactMap(Int.init) print (compactMapped)

Wann immer Sie den Initializer
init?()
Als Transformationsfunktion verwenden, müssen Sie
compactMap(_ :)
:
Ich muss sagen, dass es mehr als genug Gründe gibt, die Funktion
compactMap(_ :)
höherer Ordnung
compactMap(_ :)
.
Swift
"liebt"
Optional
Werte, die nicht nur mit dem Initializer "
failable
"
init?()
,
failable
auch mit dem
as?
"Casting"
:
let views = [innerView,shadowView,logoView] let imageViews = views.compactMap{$0 as? UIImageView}
... und die
try?
bei der Verarbeitung von Fehlern, die von einigen Methoden ausgelöst werden. Ich muss sagen, dass
Apple
besorgt ist, dass die Verwendung von
try?
führt sehr oft zu doppeltem
Optional
und lässt in
Swift 5 nur noch ein
Optional
Level nach dem Anwenden von
try?
.
Es gibt eine weitere ähnliche Funktion im Namen der
flatMap(_ :)
höherer Ordnung
flatMap(_ :)
, über die etwas niedriger ist.
Um die Funktionszuordnung höherer Ordnung
map(_:)
, ist es manchmal nützlich, die Methode
zip (_:, _:)
verwenden, um eine Folge von Paaren aus zwei verschiedenen Folgen zu erstellen.
Angenommen, wir haben eine
view
in der mehrere Punkte dargestellt werden, die miteinander verbunden sind und eine gestrichelte Linie bilden:

Wir müssen eine weitere unterbrochene Linie erstellen, die die Mittelpunkte der Segmente der ursprünglichen unterbrochenen Linie verbindet:

Um den Mittelpunkt eines Segments zu berechnen, müssen wir die Koordinaten von zwei Punkten haben: dem aktuellen und dem nächsten. Dazu können wir mit der
zip (_:, _:)
Methode
zip (_:, _:)
eine Sequenz aus Punktpaaren - dem aktuellen und dem nächsten - bilden, in der wir das Startpunktarray und ein Array der folgenden
points.dropFirst()
:
let pairs = zip (points,points.dropFirst()) let averagePoints = pairs.map { CGPoint(x: ($0.x + $1.x) / 2, y: ($0.y + $1.y) / 2 )}
Mit einer solchen Sequenz berechnen wir die Mittelpunkte sehr einfach mithilfe der Funktionskarte höherer Ordnung
map(_:)
und zeigen sie in der Grafik an.
filter (_:)
In
Swift
ist der Funktionsfilter höherer Ordnung
filter (_:)
die meisten
verfügbar,
die die
map(_:)
Funktion
map(_:)
verfügbar ist. Sie können beliebige
Sequence
mit
filter (_:)
, das liegt auf der Hand! Die Methode
filter (_:)
verwendet eine andere Funktion als Parameter, die eine Bedingung für jedes Element der Sequenz darstellt. Wenn die Bedingung erfüllt ist, wird das Element in das Ergebnis aufgenommen, und wenn nicht, wird es nicht berücksichtigt. Diese "andere Funktion" nimmt einen einzelnen Wert an - ein Element der
Sequence
- und gibt einen
Bool
, das sogenannte Prädikat.
Bei
Array
Arrays wendet beispielsweise der Funktionsfilter höherer Ordnung
filter (_:)
die Prädikatfunktion an und gibt ein anderes Array zurück, das ausschließlich aus den Elementen des ursprünglichen Arrays besteht, für die die Eingabe-Prädikatfunktion
true
zurückgibt.
Hier nimmt der Funktionsfilter höherer Ordnung
filter (_:)
jedes Element des
numbers
(dargestellt durch
$0
) und prüft, ob dieses Element eine gerade Zahl ist. Wenn dies eine gerade Zahl ist, fallen die Elemente des
numbers
in das neue
filted
Array, andernfalls nicht. Wir haben dem Programm in einer deklarativen Form mitgeteilt,
wir bekommen möchten, anstatt uns darum zu kümmern, wie wir es tun sollen.
Ich werde ein weiteres Beispiel für die Verwendung des Funktionsfilters höherer Ordnung
filter (_:)
, um nur die geraden ersten
20
Fibonacci-Zahlen mit Werten
< 4000
:
let fibonacci = sequence(first: (0, 1), next: { ($1, $0 + $1) }) .prefix(20).map{$0.0} .filter {$0 % 2 == 0 && $0 < 4000} print (fibonacci)
Wir erhalten eine Folge von Tupeln, die aus zwei Elementen der Fibonacci-Folge besteht: dem n-ten und (n + 1) -ten:
(0, 1), (1, 1), (1, 2), (2, 3), (3, 5) …
Zur weiteren Verarbeitung begrenzen wir die Anzahl der Elemente mit dem
prefix (20)
auf die einundzwanzigsten Elemente und nehmen das
0
Element des gebildeten Tupels mit
map {$0.0 }
, das der Fibonacci-Sequenz ab
0
:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,...
Wir könnten das
1
Element des gebildeten Tupels mit der
map {$0.1 }
, die der Fibonacci-Sequenz entspricht, die mit
1
beginnt:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,...
Wir erhalten die benötigten Elemente mithilfe des Funktionsfilters höherer Ordnung
filter {$0 % 2 == 0 && $0 < 4000}
, der ein Array von Sequenzelementen zurückgibt, die das angegebene Prädikat erfüllen. In unserem Fall handelt es sich um ein Array von Ganzzahlen
[Int]
:
[0, 2, 8, 34, 144, 610, 2584]
Es gibt ein weiteres nützliches Beispiel für die Verwendung von
filter (_:)
für eine
Collection
.
Ich bin auf
ein echtes Problem gestoßen, wenn Sie eine Reihe von Bildern haben, die mit
CollectionView
angezeigt werden, und Sie können die
Drag & Drop
Technologie verwenden, um ein ganzes „Paket“ von Bildern zu sammeln und sie überall hin zu verschieben, einschließlich „Dumping“ auf „ Mülltonne".

In diesem Fall ist das Array der entfernten Indizes
removedIndexes
die in den "Papierkorb"
removedIndexes
behoben, und Sie müssen ein neues Array von Bildern erstellen, mit Ausnahme derjenigen, deren Indizes sich im Array "
removedIndexes
. Angenommen, wir haben ein Array von Ganzzahlbildern, die Bilder imitieren, und ein Array von Indizes dieser Ganzzahlen
removedIndexes
, die entfernt werden müssen. Wir werden
filter (_:)
, um unser Problem zu lösen:
var images = [6, 22, 8, 14, 16, 0, 7, 9] var removedIndexes = [2,5,0,6] var images1 = images .enumerated() .filter { !removedIndexes.contains($0.offset) } .map { $0.element } print (images1)
Die Methode
enumerated()
gibt eine Folge von Tupeln zurück, die aus Versatzindizes und Arrayelementwerten besteht.
Dann wenden wir einen Filter filter
auf die resultierende Folge von Tupeln an und lassen nur diejenigen übrig, deren Index $0.offset
nicht im Array enthalten ist removedIndexes
. Im nächsten Schritt wählen wir den Wert aus dem Tupel aus $0.element
und erhalten das Array, das wir benötigen images1
.reduce (_:, _:)
Die Methode steht reduce (_:, _:)
auch den meisten
verfügbaren Methoden map(_:)
und Methoden zur Verfügung filter (_:)
. Die Methode reduce (_:, _:)
"reduziert" die Sequenz Sequence
auf einen einzelnen Akkumulationswert und hat zwei Parameter. Der erste Parameter ist der Startakkumulationswert, und der zweite Parameter ist eine Funktion, die den Akkumulationswert mit dem Sequenzelement kombiniert Sequence
, um einen neuen Akkumulationswert zu erhalten.Die Eingabeparameterfunktion wird nacheinander auf jedes Element der Sequenz angewendet Sequence
, bis es das Ende erreicht und den endgültigen Akkumulationswert erzeugt. let sum = Array (1...100).reduce(0, +)
Dies ist ein klassisches triviales Beispiel für die Verwendung einer Funktion höherer Ordnung reduce (_:, _:)
- das Zählen der Summe der Elemente eines Arrays Array
. 1 0 1 0 +1 = 1 2 1 2 2 + 1 = 3 3 3 3 3 + 3 = 6 4 6 4 4 + 6 = 10 . . . . . . . . . . . . . . . . . . . 100 4950 100 4950 + 100 = 5050
Mit der Funktion können reduce (_:, _:)
wir ganz einfach die Summe der Fibonacci-Zahlen berechnen, die eine bestimmte Bedingung erfüllen: let fibonacci = sequence(first: (0, 1), next: { ($1, $0 + $1) }) .prefix(20).map{$0.0} .filter {$0 % 2 == 0 && $0 < 4000} print (fibonacci)
Es gibt jedoch interessantere Anwendungen einer Funktion höherer Ordnung reduce (_:, _:)
.Zum Beispiel können wir sehr einfach und präzise einen sehr wichtigen Parameter für UIScrollView
- die Größe des "scrollbaren" Bereichs contentSize
- basierend auf seiner Größe bestimmen subviews
: let scrollView = UIScrollView() scrollView.addSubview(UIView(frame: CGRect(x: 300.0, y: 0.0, width: 200, height: 300))) scrollView.addSubview(UIView(frame: CGRect(x: 100.0, y: 0.0, width: 300, height: 600))) scrollView.contentSize = scrollView.subviews .reduce(CGRect.zero,{$0.union($1.frame)}) .size
In dieser Demo ist der Akkumulationswert
GCRect
und die Akkumulationsoperation ist die Operation zum Kombinieren der union
Rechtecke, die frame
uns gehören subviews
.Trotz der Tatsache, dass eine Funktion höherer Ordnung reduce (_:, _:)
einen akkumulativen Charakter annimmt, kann sie in einer völlig anderen Perspektive verwendet werden. So teilen Sie beispielsweise ein Tupel in Teile einer Reihe von Tupeln auf:
Swift 4.2
führte eine neue Art von Funktion höherer Ordnung ein reduce (into:, _:)
. Das Verfahren reduce (into:, _:)
ist dadurch vorteilhaft, die Effizienz im Vergleich mit dem Verfahren reduce (:, :)
, wenn die sich ergebende Struktur verwendet wird COW (copy-on-write)
, beispielsweise, Array
oder Dictionary
.Es kann effektiv verwendet werden, um übereinstimmende Werte in einem Array von Ganzzahlen zu entfernen:
... oder beim Zählen der Anzahl verschiedener Elemente in einem Array:
flatMap (_:)
Bevor wir zu dieser Funktion höherer Ordnung übergehen, schauen wir uns eine sehr einfache Demo an. let maybeNumbers = ["42", "7", "three", "///4///", "5"] let firstNumber = maybeNumbers.map (Int.init).first
Wenn wir diesen Code ausführen Playground
, um ihn auszuführen , sieht alles gut aus, und unser Code firstNumber
ist gleich 42
:
Wenn Sie es nicht wissen, Playground
verbirgt er häufig den wahren Code
, insbesondere
Konstanten firstNumber
. In der Tat, die konstant firstNumber
ist
doppelt Optional
:
Dies tritt auf, weil map (Int.init)
das Ausgangsarray erzeugt Optional
Typwerte [Int?]
als jede Zeile nicht ist , String
zu transformieren kann Int
und Initialisierer Int.int
wird „fallenden“ ( failable
). Dann nehmen wir das erste Element des gebildeten Arrays mit der Funktion first
für das Array Array
, die auch die Ausgabe bildetOptional
, da das Array möglicherweise leer ist und wir das erste Element des Arrays nicht erhalten können. Als Ergebnis haben wir ein Doppel Optional
, d.h.Int??
.
Wir haben eine verschachtelte Struktur, Optional
in Optional
der es wirklich schwieriger ist zu arbeiten und die wir natürlich nicht haben wollen. Um den Wert dieser verschachtelten Struktur zu erhalten, müssen wir in zwei Ebenen „eintauchen“. Darüber hinaus können zusätzliche Transformationen den Pegel Optional
noch weiter vertiefen .Es ist Optional
wirklich mühsam, den Wert aus dem doppelt verschachtelten herauszuholen .Wir haben 3 Optionen und alle erfordern fundierte Sprachkenntnisse Swift
.if let
, ; «» «» Optional
, — «» Optional
:

if case let
( pattern match
) :

??
:

- ,
switch
:

Schlimmer noch, solche Verschachtelungsprobleme
treten in allen Situationen auf, in denen generalisierte ( generic
) Container enthalten sind, für die eine Operation definiert ist map
. Zum Beispiel für Arrays Array
.Betrachten Sie einen anderen Beispielcode. Angenommen, wir haben einen mehrzeiligen Text multilineString
, den wir in Wörter in Kleinbuchstaben (Kleinbuchstaben) unterteilen möchten: let multilineString = """ , , ; , — , : — , . , , . . , , « » . , , ! """ let words = multilineString.lowercased() .split(separator: "\n") .map{$0.split(separator: " ")}
Um ein Array von Wörtern zu erhalten words
, machen wir zuerst Großbuchstaben (groß) mit der Methode in Kleinbuchstaben (klein) lowercased()
. Dann teilen wir den Text mit der Methode in split(separatot: "\n")
Zeilen und erhalten ein Array von Zeichenfolgen. Anschließend map {$0.split(separator: " ")}
trennen wir jede Zeile in separate Wörter.Als Ergebnis erhalten wir verschachtelte Arrays: [["", ",", "", ","], ["", "", ";", "", "", "", "", ",", "—"], ["", ",", "", "", ":"], ["", "—", "", "", ",", "", "", "."], ["", "", ",", "", "", ","], ["", "", ".", "", ""], ["", ".", "", ",", ""], ["", "", "", ""], ["", "", ",", "", "«", "»"], ["", ".", "", ","], ["", ",", "", "", "!"]]
... und es words
hat
zwei Dinge Array
:
Wir haben wieder eine "verschachtelte" Datenstruktur, aber diesmal haben wir es nicht getan Optional
, aber Array
. Wenn wir die empfangenen Wörter weiter verarbeiten möchten words
, um beispielsweise das Buchstabenspektrum dieses mehrzeiligen Textes zu finden, müssen wir zuerst das Array des Doppelten irgendwie „begradigen“ Array
und es in ein einzelnes Array verwandeln Array
. Dies ähnelt dem, was wir mit double Optional
für eine Demo am Anfang dieses Abschnitts gemacht haben flatMap
: let maybeNumbers = ["42", "7", "three", "///4///", "5"] let firstNumber = maybeNumbers.map (Int.init).first
Glücklicherweise müssen Swift
wir nicht auf komplexe syntaktische Konstruktionen zurückgreifen. Swift
liefert uns eine fertige Lösung für Arrays Array
und Optional
. Dies ist eine Funktion höherer Ordnung flatMap
! Es ist sehr ähnlich, map
verfügt jedoch über zusätzliche Funktionen, die mit dem anschließenden "Begradigen" der "Anhänge" verbunden sind, die während der Ausführung angezeigt werden map
. Und deshalb heißt es flatMap
, es „begradigt“ ( flattens
) das Ergebnis map
.Lassen Sie sich bewerben flatMap
zu firstNumber
:
Wir haben wirklich den Ausgangswert von c einziger Ebene bekommen Optional
. Funktioniert für ein Arraynoch interessanter . In unseren Bedingungen, denn wir einfach ersetzen aufflatMap
Array
words
map
flatMap
:
... und wir bekommen nur eine Reihe von Wörtern words
ohne "Verschachtelung": ["", ",", "", ",", "", "", ";", "", "", "", "", ",", "—", "", ",", "", "", ":", "", "—", "", "", ",", "", "", ".", "", "", ",", "", "", ",", "", "", ".", "", "", "", ".", "", ",", "", "", "", "", "", "", "", ",", "", "«", "»", "", ".", "", ",", "", ",", "", "", "!"]
Jetzt können wir die Verarbeitung der resultierenden Wortreihe fortsetzen words
, aber seien Sie vorsichtig. Wenn wir es erneut flatMap
auf jedes Element des Arrays anwenden words
, erhalten wir möglicherweise ein unerwartetes, aber durchaus verständliches Ergebnis.
Wir erhalten eine einzelne, nicht "verschachtelte" Anordnung von Buchstaben und Symbolen, [Character]
die in unserer mehrzeiligen Phrase enthalten sind: ["", "", "", "", "", "", "", "", "", "", "", "", ",", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ",", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ";", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ...]
Tatsache ist, dass die Zeichenfolge String
eine Sammlung von Collection
Zeichen [Character]
ist. Wenn flatMap
wir auf jedes einzelne Wort zutreffen, senken wir erneut die Ebene der "Verschachtelung" und kommen zu einer Reihe von Zeichen flattenCharacters
.Vielleicht ist es genau das, was Sie wollen, oder vielleicht auch nicht. Achten Sie darauf.Alles zusammenfügen: einige Probleme lösen
AUFGABE 1
Wir können die Verarbeitung des im vorherigen Abschnitt erhaltenen Wortarrays fortsetzen words
und die Häufigkeit des Auftretens von Buchstaben in unserer mehrzeiligen Phrase berechnen. Lassen Sie uns zunächst alle Wörter aus dem Array words
in eine große Zeile „kleben“ und alle Satzzeichen davon ausschließen, dh nur die Buchstaben belassen: let wordsString = words.reduce ("",+).filter { "" .contains($0)}
Also haben wir alle Briefe, die wir brauchen. Lassen Sie uns nun ein Wörterbuch daraus erstellen, in dem der Schlüssel key
der Buchstabe und der Wert value
die Häufigkeit seines Auftretens im Text ist.Wir können dies auf zwei Arten tun.Die erste Methode ist mit der Verwendung einer neuen Swift 4.2
Variante einer Funktion höherer Ordnung verbunden , die in erschienen ist reduce (into:, _:)
. Diese Methode eignet sich gut für die Organisation eines Wörterbuchs letterCount
mit der Häufigkeit des Auftretens von Buchstaben in unserer mehrzeiligen Phrase: let letterCount = wordsString.reduce(into: [:]) { counts, letter in counts[letter, default: 0] += 1} print (letterCount)
Als Ergebnis erhalten wir ein Wörterbuch, letterCount
[Character : Int]
in dem die Schlüssel key
die Zeichen sind, die in der untersuchten Phrase gefunden werden, und als Wert value
die Anzahl dieser Zeichen.Die zweite Methode umfasst das Initialisieren des Wörterbuchs mithilfe der Gruppierung, wodurch das gleiche Ergebnis erzielt wird: let letterCountDictionary = Dictionary(grouping: wordsString ){ $0}.mapValues {$0.count} letterCount == letterCountDictionary
Wir möchten das Wörterbuch letterCount
alphabetisch sortieren : let lettersStat = letterCountDictionary .sorted(by: <) .map{"\($0.0):\($0.1)"} print (lettersStat)
Wir können das Wörterbuch jedoch nicht direkt sortieren Dictionary
, da es sich grundsätzlich nicht um eine geordnete Datenstruktur handelt. Wenn wir die Funktion sorted (by:)
auf das Wörterbuch anwenden Dictionary
, werden die Elemente der Sequenz, die mit dem angegebenen Prädikat sortiert sind, in Form eines Arrays benannter Tupel an map
uns zurückgegeben , die wir in ein Array von Zeichenfolgen [":17", ":5", ":18", ...]
umwandeln, die die Häufigkeit des Auftretens des entsprechenden Buchstabens widerspiegeln.Wir sehen, dass diesmal sorted (by:)
nur der Operator " <
" als Prädikat an eine Funktion höherer Ordnung übergeben wird . Die Funktion sorted (by:)
erwartet eine „Vergleichsfunktion“ als einziges Argument am Eingang. Es wird verwendet, um zwei benachbarte Werte zu vergleichen und zu entscheiden, ob sie korrekt sortiert sind (in diesem Fall wird zurückgegebentrue
) oder nicht (kehrt zurück false
). Wir können dieser "Vergleichsfunktion" Funktionen sorted (by:)
in Form eines anonymen Abschlusses geben: sorted(by: {$0.key < $1.key}
Und wir können ihm einfach den Operator " <
" geben, der die Signatur hat, die wir brauchen, wie oben beschrieben. Dies ist auch eine Funktion, und die Sortierung nach Schlüsseln wird ausgeführt key
.Wenn wir das Wörterbuch nach Werten sortieren value
und herausfinden möchten , welche Buchstaben in dieser Phrase am häufigsten vorkommen, müssen wir den Abschluss für die Funktion verwenden sorted (by:)
: let countsStat = letterCountDictionary .sorted(by: {$0.value > $1.value}) .map{"\($0.0):\($0.1)"} print (countsStat )
Wenn wir uns die Lösung für das Problem der Bestimmung des Buchstabenspektrums einer mehrzeiligen Phrase als Ganzes ansehen ... let multilineString = """ , , ; , — , : — , . , , . . , , « » . , , ! """ let words = multilineString.lowercased() .split(separator: "\n") .flatMap{$0.split(separator: " ")} let wordsString = words.reduce ("",+).filter { "" .contains($0)} let letterCount = wordsString.reduce(into: [:]) { counts, letter in counts[letter, default: 0] += 1} let lettersStat = letterCountDictionary .sorted(by: <) .map{"\($0.0):\($0.1)"} print (lettersStat)
... dann werden wir feststellen, dass es in diesem Codefragment grundsätzlich keine Variablen gibt (nein var
, nur let)
alle Namen der verwendeten Funktionen spiegeln AKTIONEN (Funktionen) über bestimmte Informationen wider, ohne sich Gedanken darüber zu machen, wie diese Aktionen implementiert werden:split
- split,map
- transformflatMap
- transform with Ausrichtung (um eine Stufe der Verschachtelung zu entfernen),filter
- Filter,sorted
- Vereinzeln,reduce
- Daten in eine bestimmte Struktur zu drehen mittels einer spezifischen Operationin diesem Fragmente jeder Codezeile erklärt den Namen der Funktion , die wir verwenden , wenn wir sind. „Reine“ füllt die Transformation verwendet wird , map
wenn wir Umwandlung der Verschachtelungsebene auszuführen verwendet wirdflatMap
Wenn wir nur bestimmte Daten auswählen möchten, verwenden wir filter
usw. Alle diese Funktionen "höchster Ordnung" werden Apple
unter Berücksichtigung der Leistungsoptimierung entworfen und getestet . Dieser Code ist also sehr zuverlässig und prägnant - wir brauchten nicht mehr als 5 Sätze, um unser Problem zu lösen. Dies ist ein Beispiel für eine funktionale Programmierung.Der einzige Nachteil bei der Anwendung des funktionalen Ansatzes in dieser Demo besteht darin, dass wir unseren Text aus Gründen der Unveränderlichkeit, Testbarkeit und Lesbarkeit wiederholt durch verschiedene Funktionen höherer Ordnung verfolgen. Bei einer großen Anzahl von Sammlungsgegenständen kann die Collection
Leistung sinken. Zum Beispiel, wenn wir zuerst filter(_:)
und und dann - verwenden first
.InSwift 4
Einige neue Funktionsoptionen wurden hinzugefügt, um die Leistung zu verbessern. Hier finden Sie einige Tipps zum Schreiben von schnellerem Code.1. contains
NICHT verwendenfirst( where: ) != nil
Das Überprüfen, ob sich ein Objekt in einer Sammlung befindet, Collection
kann auf verschiedene Arten erfolgen. Die beste Leistung liefert die Funktion contains
.RICHTIGER CODE let numbers = [0, 1, 2, 3] numbers.contains(1)
Falscher Code let numbers = [0, 1, 2, 3] numbers.filter { number in number == 1 }.isEmpty == false numbers.first(where: { number in number == 1 }) != nil
2. Verwenden Sie die Validierung isEmpty
, NICHT einen Vergleich count
mit Null
Da für einige Sammlungen der Zugriff auf die Eigenschaft count
durch Iteration über alle Elemente der Sammlung erfolgt.RICHTIGER CODE let numbers = [] numbers.isEmpty
Falscher Code let numbers = [] numbers.count == 0
3. Überprüfen Sie die leere Zeichenfolge String
mitisEmpty
String String
in Swift
ist eine Sammlung von Zeichen [Character]
. Dies bedeutet, dass es für Strings String
auch besser ist, diese zu verwenden isEmpty
.RICHTIGER CODE myString.isEmpty
Falscher Code myString == "" myString.count == 0
4. Erhalten des ersten Elements, das bestimmte Bedingungen erfüllt
Das Durchlaufen der gesamten Sammlung, um das erste Objekt zu erhalten, das bestimmte Bedingungen erfüllt, kann mit einer Methode filter
gefolgt von einer Methode durchgeführt first
werden. Die Methode ist jedoch hinsichtlich der Geschwindigkeit die beste first (where:)
. Diese Methode beendet die Iteration über die Sammlung, sobald sie die erforderliche Bedingung erfüllt. Die Methode filter
iteriert weiterhin über die gesamte Sammlung, unabhängig davon, ob sie die erforderlichen Elemente erfüllt oder nicht.Dies gilt natürlich auch für die Methode last (where:)
.RICHTIGER CODE let numbers = [3, 7, 4, -2, 9, -6, 10, 1] let firstNegative = numbers.first(where: { $0 < 0 })
Falscher Code let numbers = [0, 2, 4, 6] let allEven = numbers.filter { $0 % 2 != 0 }.isEmpty
Manchmal, wenn die Sammlung Collection
sehr groß ist und die Leistung für Sie von entscheidender Bedeutung ist, lohnt es sich, wieder die zwingenden und funktionalen Ansätze zu vergleichen und den für Sie geeigneten auszuwählen.AUFGABE 2
Es gibt ein weiteres großartiges Beispiel für eine sehr präzise Verwendung einer Funktion höherer Ordnung reduce (_:, _:)
, auf die ich gestoßen bin. Dies ist ein SET- Spiel .Hier sind die Grundregeln. Der Name des Spiels SET
stammt vom englischen Wort "set" - "set". Das Spiel SET
besteht aus 81 Karten mit jeweils einem einzigartigen Bild:
Jede Karte hat 4 Attribute, die unten aufgeführt sind:Anzahl : Jede Karte hat ein, zwei oder drei Zeichen.Art der Zeichen : Ovale, Rauten oder Wellen.Farbe : Symbole können rot, grün oder lila sein.Füllen : Zeichen können leer, schattiert oder schattiert sein.Zweck des SpielsSET
: Unter den 12 Karten, die auf dem Tisch ausgelegt sind, müssen Sie SET
(einen Satz) finden, der aus 3 Karten besteht, wobei jedes der Zeichen auf allen 3 Karten entweder vollständig übereinstimmt oder sich vollständig unterscheidet. Alle Zeichen müssen dieser Regel vollständig entsprechen.Zum Beispiel sollte die Anzahl der Zeichen auf allen 3 Karten entweder gleich oder unterschiedlich sein, die Farbe auf allen 3 Karten sollte entweder gleich oder verschieden sein usw.In diesem Beispiel werden wir uns nur für das Kartenmodell SET
struct SetCard
und den Algorithmus zur Bestimmung SET
durch interessieren 3. Karten isSet( cards:[SetCard])
: struct SetCard: Equatable { let number: Variant
Die Modelle jedes Merkmal - Nummer number
, Symboltyp shape
, Farbe color
und Füllung fill
- präsentiert Listing Variant
drei mögliche Werte aufweisen var1
, var2
und var3
das entspricht die dritten ganzen Zahlen rawValue
- 1,2,3
. In dieser Form ist rawValue
es einfach zu bedienen. Wenn wir zum Beispiel ein Zeichen nehmen und color
dann alles rawValue
für colors
3 Karten hinzufügen , werden wir feststellen, dass wenn colors
für alle 3 Karten gleich ist, die Summe gleich 3
ist 6
oder 9
, und wenn sie alle unterschiedlich sind, dann ist die Summe gleich gleich 6
. In jedem dieser Fälle haben wir die Multiplizität der 3. Summe rawValue
fürcolors
alle 3 Karten. Wir wissen, dass dies eine Voraussetzung dafür ist, was 3 Karten ausmachen SET
. Damit 3 Karten SET
für alle Zeichen SetCard
- Menge number
, Symboltyp shape
, Farbe color
und Füllung fill
- wirklich notwendig werden, sollte ihre Summe rawValue
ein Vielfaches der 3 sein.Daher wird in dem static
Verfahren, isSet( cards:[SetCard])
wir zuerst das Array berechnen sums
Summen rawValue
für alle 3 Karten für alle 4 Leistungskarten Funktion höherer Ordnung unter Verwendung von reduce
mit einem anfänglichen Wert gleich 0
, und Funktionen Akkumulieren {$0 + $1.number.rawValue}
, {$0 + $1.color.rawValue}
, {$0 + $1.shape.rawValue}
, { {$0 + $1.fill.rawValue}
. Jedes Element des Arrays sums
muss ein Vielfaches von 3 sein, und wieder verwenden wir die Funktionreduce
, diesmal jedoch mit einem Anfangswert, der der true
logischen Funktion " AND
" entspricht und diese akkumuliert {$0 && ($1 % 3) == 0}
. In Swift 5 wird isMultiply(of:)
anstelle des %
Restoperators eine Funktion eingeführt , um die Vielzahl einer Zahl für eine andere zu testen . Es wird auch die Lesbarkeit des Codes verbessern: { $0 && ($1.isMultiply(of:3) }
.Dieser fantastisch kurze Code, um herauszufinden, ob 3 SetCard
Karten das SET
i-te sind, wird dank des " funktionalen " Ansatzes erhalten, und wir können sicherstellen, dass er funktioniert Playground
:
Wie man SET
die Benutzeroberfläche ( UI
) auf diesem Spielmodell hier , hier und hier erstellt .Reine Merkmale und Nebenwirkungen
Eine reine Funktion erfüllt zwei Bedingungen. Es wird immer das gleiche Ergebnis mit den gleichen Eingabeparametern zurückgegeben. Und die Berechnung des Ergebnisses verursacht keine Nebenwirkungen, die mit der Ausgabe von Daten außerhalb (z. B. auf die Festplatte) oder mit der Ausleihe von Quelldaten von außerhalb (z. B. Zeit) verbunden sind. Auf diese Weise können Sie den Code erheblich optimieren.Dieses Thema ist in den ersten Folgen von " Funktionen " und " Nebenwirkungen " , die ins Russische übersetzt und als " Funktionen " und "Nebenwirkungen " dargestellt werden , Swift
perfekt auf point.free dargestellt .Funktionszusammensetzung
In mathematischer Hinsicht bedeutet dies, eine Funktion auf das Ergebnis einer anderen Funktion anzuwenden. In einer Swift
Funktion können sie einen Wert zurückgeben, den Sie als Eingabe für eine andere Funktion verwenden können. Dies ist eine gängige Programmierpraxis.Stellen Sie sich vor, wir haben ein Array von ganzen Zahlen und möchten ein Array von Quadraten mit eindeutigen geraden Zahlen am Ausgang erhalten. Normalerweise implementieren wir dies wie folgt neu: var integerArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 4, 5] func unique(_ array: [Int]) -> [Int] { return array.reduce(into: [], { (results, element) in if !results.contains(element) { results.append(element) } }) } func even(_ array: [Int]) -> [Int] { return array.filter{ $0%2 == 0} } func square(_ array: [Int]) -> [Int] { return array.map{ $0*$0 } } var array = square(even(unique(integerArray)))
Dieser Code liefert das richtige Ergebnis, aber Sie sehen, dass die Lesbarkeit der letzten Codezeile nicht so einfach ist. Die Reihenfolge der Funktionen (von rechts nach links) ist das Gegenteil von der, an die wir gewöhnt sind (von links nach rechts) und die wir hier sehen möchten. Wir müssen unsere Logik zuerst auf den innersten Teil mehrerer Einbettungen richten - auf das Array inegerArray
, dann auf die Funktion außerhalb dieses Arrays unique
, dann auf eine andere Ebene - die Funktion even
und schließlich die Funktion in der Schlussfolgerung square
.Und hier hilft uns die „Zusammensetzung“ von Funktionen >>>
und Operatoren |>
, die es uns ermöglicht, den Code auf sehr bequeme Weise zu schreiben und die Verarbeitung des ursprünglichen Arrays integerArray
als „Förderer“ von Funktionen darzustellen : var array1 = integerArray |> unique >>> even >>> square
Fast alle Sprachen wie funktionale Programmierung spezialisiert F#
, Elixir
und Elm
diese Operatoren für „Komposition“ -Funktionen verwenden.Es Swift
gibt keine eingebauten Operatoren für die „Zusammensetzung“ von Funktionen >>>
und |>
, aber wir können sie sehr einfach mit Hilfe von Generics
Closures ( closure
) und dem infix
Operator abrufen: precedencegroup ForwardComposition{ associativity: left higherThan: ForwardApplication } infix operator >>> : ForwardComposition func >>> <A, B, C>(left: @escaping (A) -> B, right: @escaping (B) -> C) -> (A) -> C { return { right(left($0)) } } precedencegroup ForwardApplication { associativity: left } infix operator |> : ForwardApplication func |> <A, B>(a: A, f: (A) -> B) -> B { return f(a) }
Trotz der zusätzlichen Kosten kann dies in einigen Fällen die Leistung, Lesbarkeit und Testbarkeit Ihres Codes erheblich verbessern. Wenn Sie beispielsweise map
eine ganze Funktionskette mit dem Operator "Komposition" platzieren, >>>
anstatt ein Array durch zahlreiche zu jagen map
: var integerArray1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 4, 5] let b = integerArray1.map( { $0 + 1 } >>> { $0 * 3 } >>> String.init) print (b)
Ein funktionaler Ansatz wirkt sich jedoch nicht immer positiv aus.Als es Swift
2014 erschien, beeilten sich alle, Bibliotheken mit Operatoren für die „Komposition“ von Funktionen zu schreiben und eine schwierige Aufgabe zu lösen, wie das Parsen JSON
mit funktionalen Programmieroperatoren anstelle von unendlich verschachtelten Konstruktionen if let
. Ich selbst habe den Artikel über das funktionale Parsen von JSON übersetzt, der mich mit seiner eleganten Lösung begeisterte und ein Fan der Argo- Bibliothek war .Die Entwickler Swift
gingen jedoch einen völlig anderen Weg und schlugen auf der Grundlage der protokollorientierten Technologie eine viel präzisere Art des Schreibens von Code vor. Um die JSON
Daten direkt an zu "liefern"
Genug , um dies zu tun
Codable
, die automatisch dieses Protokoll implementiert, wenn Ihr Modell der bekannten besteht Swift
Datenstrukturen: String
, Int
, URL
, Array
, Dictionary
, usw. struct Blog: Codable { let id: Int let name: String let url: URL }
Mit JSON
Daten von einem berühmten Artikel ... [ { "id" : 73, "name" : "Bloxus test", "url" : "http://remote.bloxus.com/" }, { "id" : 74, "name" : "Manila Test", "url" : "http://flickrtest1.userland.com/" } ]
... im Moment benötigen Sie nur eine Codezeile, um eine Reihe von Blogs zu erhalten blogs
: let blogs = Bundle.main.path(forResource: "blogs", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Blog].self, from: $0) } print ("\(blogs!)")
Jeder hat sicher vergessen, die Operatoren der „Zusammensetzung“ von Funktionen zum Parsen zu verwenden JSON
, wenn es eine andere, verständlichere und einfachere Möglichkeit gibt, dies mithilfe von Protokollen zu tun.Wenn alles so einfach ist, können wir JSON
Daten in komplexere Modelle „hochladen“ . Angenommen, wir haben eine Datendatei JSON
, die einen Namen hat user.json
und sich in unserem Verzeichnis befindet Resources.
. Sie enthält Daten zu einem bestimmten Benutzer: { "email": "blob@pointfree.co", "id": 42, "name": "Blob" }
Und wir haben einen Codable
Benutzer User
mit einem Initialisierer aus den Daten json
: struct User: Codable { let email: String let id: Int let name: String init?(json: Data) { if let newValue = try? JSONDecoder().decode(User.self, from: json) { self = newValue } else { return nil } } }
Wir können sehr leicht einen neuen Benutzer newUser
mit einem noch einfacheren Funktionscode finden: let newUser = Bundle.main.path(forResource: "user", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { User.init(json: $0) }
Offensichtlich wird der TYP newUser
sein Optional
, d.h.User?
::
Angenommen, wir Resources
haben eine andere Datei mit einem Namen im Verzeichnis invoices.json
, die Informationen zu den Rechnungen dieses Benutzers enthält. [ { "amountPaid": 1000, "amountDue": 0, "closed": true, "id": 1 }, { "amountPaid": 500, "amountDue": 500, "closed": false, "id": 2 } ]
Wir können diese Daten genau so laden, wie wir es getan haben User
. Definieren wir die Struktur als Rechnungsmodell struct Invoice
... struct Invoice: Codable { let amountDue: Int let amountPaid: Int let closed: Bool let id: Int }
... und dekodieren Sie das oben dargestellte JSON
Rechnungsarray invoices
, indem Sie nur den Dateipfad und die Dekodierungslogik ändern decode
: let invoices = Bundle.main.path(forResource: "invoices", ofType: "json") .map( URL.init(fileURLWithPath:) ) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) }
invoices
wird sein [Invoice]?
::
Jetzt möchten wir den Benutzer user
mit seinen Rechnungen verbinden invoices
, wenn diese nicht gleich sind nil
, und beispielsweise in der Struktur des Umschlags speichern, UserEnvelope
der zusammen mit seinen Rechnungen an den Benutzer gesendet wird: struct UserEnvelope { let user: User let invoices: [Invoice] }
Anstatt zweimal aufzutreten if let
... if let newUser = newUser, let invoices = invoices { }
... sie schreibt ein funktionelles Analogdoppel if let
als Generic
Hilfsfunktion zip
, die zwei Transformieren Optional
Werte in Optional
einem Tupel: func zip<A, B>(_ a: A?, _ b: B?) -> (A, B)? { if let a = a, let b = b { return (a, b) } return nil }
Jetzt haben wir keinen Grund, den Variablen etwas zuzuweisen, newUser
und invoices
wir bauen einfach alles in unsere neue Funktion ein zip
, verwenden den Initialisierer UserEnvelope.init
und alles wird funktionieren! let userEnv = zip( Bundle.main.path(forResource: "user", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { User.init(json: $0) }, Bundle.main.path(forResource: "invoices", ofType: "json") .map(URL.init(fileURLWithPath:)) .flatMap { try? Data.init(contentsOf: $0) } .flatMap { try? JSONDecoder().decode([Invoice].self, from: $0) } ).flatMap (UserEnvelope.init) print ("\(userEnv!)")
In einem einzigen Ausdruck wird ein ganzer Algorithmus zum Liefern von JSON
Daten an einen komplexen
in Form einer Struktur gepackt struct UserEnvelope
.zip
, , . user
, JSON
, invoices
, JSON
. .map
, , «» .flatMap
, , , .
Der Betrieb zip
, map
und flatMap
ist svoebrazny domänenspezifische Sprache (domänenspezifische Sprache, DSL) für Daten zu konvertieren.Wir können diese Demo weiterentwickeln, um das asynchrone Lesen des Inhalts einer Datei als eine spezielle Funktion darzustellen , die Sie auf pointfree.co sehen können .Ich bin kein fanatischer Fan der funktionalen Programmierung überall und in allem, aber eine moderate Verwendung scheint mir ratsam.Fazit
Ich habe Beispiele für verschiedene funktionale Programmierung bieten Swf
t «out of the box“, basierend auf der Verwendung von Funktionen höherer Ordnung map
, flatMap
, reduce
, filter
und die andere für die Sequenzen Sequence
, Optional
und Result
. Sie können „Arbeitspferde“ seinen Code erzeugen in ,
besonders dann , wenn der Wert beteiligt ist
- Strukturen struct
und Aufzählungen enum
. Ein iOS
Anwendungsentwickler muss dieses Tool besitzen.Alle zusammengestellten Demos Playground
finden Sie auf Github . Wenn Sie Probleme mit dem Start haben Playground
, können Sie diesen Artikel sehen:So entfernen Sie Xcode Playground-Fehler mit den Meldungen "Launching Simulator" und "Running Playground".Referenzen:
Functional Programming in Swift: An Introduction.An Introduction to Functional Programming in Swift.The Many Faces of Flat-Map: Part 3Inside the Standard Library: Sequence.map()Practical functional programming in Swift