TL; DR gehen direkt zum letzten Beispiel
Ich werde versuchen, es noch einmal zusammenzufassen.
Definitionen
Das for
Verständnis ist eine Syntax Verknüpfung zu kombinieren flatMap
und map
in einer Weise , die über leicht zu lesen und Grund.
Vereinfachen wir die Dinge ein wenig und nehmen an, dass jede class
, die beide oben genannten Methoden bereitstellt, als a bezeichnet werden kann, monad
und wir verwenden das Symbol M[A]
, um a monad
mit einem inneren Typ zu bezeichnen A
.
Beispiele
Einige häufig gesehene Monaden sind:
List[String]
wo
M[X] = List[X]
A = String
Option[Int]
wo
Future[String => Boolean]
wo
M[X] = Future[X]
A = (String => Boolean)
map und flatMap
In einer generischen Monade definiert M[A]
/* applies a transformation of the monad "content" mantaining the
* monad "external shape"
* i.e. a List remains a List and an Option remains an Option
* but the inner type changes
*/
def map(f: A => B): M[B]
/* applies a transformation of the monad "content" by composing
* this monad with an operation resulting in another monad instance
* of the same type
*/
def flatMap(f: A => M[B]): M[B]
z.B
val list = List("neo", "smith", "trinity")
//converts each character of the string to its corresponding code
val f: String => List[Int] = s => s.map(_.toInt).toList
list map f
>> List(List(110, 101, 111), List(115, 109, 105, 116, 104), List(116, 114, 105, 110, 105, 116, 121))
list flatMap f
>> List(110, 101, 111, 115, 109, 105, 116, 104, 116, 114, 105, 110, 105, 116, 121)
zum Ausdruck bringen
Jede Zeile im Ausdruck, die das <-
Symbol verwendet, wird in einen flatMap
Aufruf übersetzt, mit Ausnahme der letzten Zeile, die in einen abschließenden map
Aufruf übersetzt wird, in der das "gebundene Symbol" auf der linken Seite als Parameter an die Argumentfunktion übergeben wird (was wir haben vorher angerufen f: A => M[B]
):
// The following ...
for {
bound <- list
out <- f(bound)
} yield out
// ... is translated by the Scala compiler as ...
list.flatMap { bound =>
f(bound).map { out =>
out
}
}
// ... which can be simplified as ...
list.flatMap { bound =>
f(bound)
}
// ... which is just another way of writing:
list flatMap f
Ein for-Ausdruck mit nur einem <-
wird in einen map
Aufruf mit dem als Argument übergebenen Ausdruck konvertiert :
// The following ...
for {
bound <- list
} yield f(bound)
// ... is translated by the Scala compiler as ...
list.map { bound =>
f(bound)
}
// ... which is just another way of writing:
list map f
Nun zum Punkt
Wie Sie sehen können, behält die map
Operation die "Form" des Originals bei monad
, so dass dies auch für den yield
Ausdruck gilt: a List
bleibt a List
mit dem durch die Operation in der Datei transformierten Inhalt yield
.
Andererseits ist jede Bindungslinie in der for
nur eine Zusammensetzung von aufeinanderfolgenden monads
, die "abgeflacht" werden muss, um eine einzelne "äußere Form" beizubehalten.
Angenommen, für einen Moment wurde jede interne Bindung in einen map
Aufruf übersetzt, aber die rechte Hand war dieselbe A => M[B]
Funktion. Sie würden M[M[B]]
für jede Zeile im Verständnis eine erhalten.
Die Absicht der gesamten for
Syntax besteht darin, die Verkettung aufeinanderfolgender monadischer Operationen (dh Operationen, die einen Wert in einer "monadischen Form" "anheben") leicht zu "reduzieren" A => M[B]
, wobei eine letzte map
Operation hinzugefügt wird , die möglicherweise eine abschließende Transformation durchführt.
Ich hoffe, dies erklärt die Logik hinter der Wahl der Übersetzung, die auf mechanische Weise angewendet wird, n
flatMap
dh verschachtelte Anrufe, die durch einen einzelnen map
Anruf abgeschlossen werden.
Ein erfundenes anschauliches Beispiel soll
die Ausdruckskraft der for
Syntax demonstrieren
case class Customer(value: Int)
case class Consultant(portfolio: List[Customer])
case class Branch(consultants: List[Consultant])
case class Company(branches: List[Branch])
def getCompanyValue(company: Company): Int = {
val valuesList = for {
branch <- company.branches
consultant <- branch.consultants
customer <- consultant.portfolio
} yield (customer.value)
valuesList reduce (_ + _)
}
Können Sie die Art von erraten valuesList
?
Wie bereits gesagt, wird die Form des monad
durch das Verständnis beibehalten, also beginnen wir mit einem List
In company.branches
und müssen mit einem enden List
.
Der innere Typ ändert sich stattdessen und wird durch den yield
Ausdruck bestimmt: welcher istcustomer.value: Int
valueList
sollte ein sein List[Int]
Lists
. Wenn Siemap
zweimal eine FunktionA => List[B]
(die eine der wesentlichen monadischen Operationen ist) über einen bestimmten Wert ausführen, erhalten Sie eine Liste [Liste [B]] (wir gehen davon aus, dass die Typen übereinstimmen). Die zum Verständnis innere Schleife setzt diese Funktionen mit der entsprechendenflatMap
Operation zusammen und "glättet" die Form der Liste [Liste [B]] in eine einfache Liste [B] ... Ich hoffe, das ist klaryield
Klauselcustomer.value
, deren Typ istInt
, daher das Ganze zu afor comprehension
bewertetList[Int]
.Ich bin kein Scala-Mega-Geist, also zögern Sie nicht, mich zu korrigieren, aber so erkläre
flatMap/map/for-comprehension
ich mir die Saga!Um zu verstehen
for comprehension
und es zu übersetzen,scala's map / flatMap
müssen wir kleine Schritte unternehmen und die komponierenden Teile verstehen -map
undflatMap
. Aber ist nichtscala's flatMap
nurmap
beiflatten
dir, frag dich! wenn ja , warum so viele Entwickler finden es so schwer , das Verständnis für sie zu bekommen oder vonfor-comprehension / flatMap / map
. Wenn Sie sich nur Scalasmap
undflatMap
Signaturen ansehen, sehen Sie, dass sie denselben Rückgabetyp zurückgebenM[B]
und mit demselben Eingabeargument arbeitenA
(zumindest den ersten Teil der Funktion, die sie übernehmen), wenn dies den Unterschied ausmacht.Unser Plan
map
.flatMap
.for comprehension
Scalas. "Scalas Karte
Scala-Kartensignatur:
Aber es fehlt ein großer Teil, wenn wir uns diese Signatur ansehen, und es ist - woher kommt das
A
? Unser Container ist vom Typ,A
daher ist es wichtig, diese Funktion im Kontext des Containers zu betrachtenM[A]
. Unser Container kann einList
Element vom Typ sein,A
und unseremap
Funktion verwendet eine Funktion, die jedes Element vom TypA
in einen Typ umwandelt.B
Anschließend wird ein Container vom TypB
(oderM[B]
) zurückgegeben.Schreiben wir die Signatur der Karte unter Berücksichtigung des Containers:
Beachten Sie eine äußerst wichtige Tatsache in Bezug auf die Karte : Sie wird automatisch in dem Ausgabecontainer gebündelt , über den
M[B]
Sie keine Kontrolle haben. Lassen Sie es uns noch einmal betonen:map
wählt den Ausgabecontainer für uns und er wird derselbe Container sein wie die Quelle, an der wir arbeiten. Für denM[A]
Container erhalten wir den gleichenM
Container nur fürB
M[B]
und sonst nichts!map
Wenn wir diese Containerisierung für uns durchführen, geben wir nur ein Mapping vonA
bis anB
und es würde es in die Box vonM[B]
wird es für uns in die Box legen!Sie sehen, Sie haben nicht angegeben, wie
containerize
das Element, das Sie gerade angegeben haben, wie die internen Elemente transformiert werden sollen. Und da wirM
für beide den gleichen Container habenM[A]
undM[B]
dies bedeutet, dassM[B]
es sich um den gleichen Container handelt, bedeutet dies, dass Sie einenList[A]
habenList[B]
und, was noch wichtigermap
ist, dies für Sie tun wird!Nun, da wir uns damit befasst haben, gehen wir
map
weiter zuflatMap
.Scalas flatMap
Mal sehen, seine Unterschrift:
Sie sehen den großen Unterschied zwischen Map und
flatMap
FlatMap. Wir bieten ihm die Funktion, die ihn nicht nur konvertiert,A to B
sondern auch in Container umwandeltM[B]
.Warum interessiert es uns, wer die Containerisierung durchführt?
Warum kümmern wir uns so sehr um die Eingabefunktion für map / flatMap, die Containerisierung in
M[B]
oder die Karte selbst übernimmt die Containerisierung für uns?Sie sehen im Zusammenhang mit dem,
for comprehension
was passiert, dass mehrere Transformationen an dem im bereitgestellten Artikel stattfinden,for
sodass wir dem nächsten Mitarbeiter in unserer Montagelinie die Möglichkeit geben, die Verpackung zu bestimmen. Stellen Sie sich vor, wir haben eine Montagelinie, an der jeder Arbeiter etwas mit dem Produkt macht und nur der letzte Arbeiter verpackt es in einem Behälter! Willkommen zuflatMap
diesem Zweckmap
jeder Arbeiter, wenn er mit der Arbeit an dem Gegenstand fertig auch verpackt, so dass Sie Container über Container bekommen.Die Mächtigen zum Verständnis
Lassen Sie uns nun Ihr Verständnis untersuchen und dabei berücksichtigen, was wir oben gesagt haben:
Was haben wir hier:
mkMatcher
Gibt a zurück,container
der Container enthält eine Funktion:String => Boolean
<-
sie übersetzt werdenflatMap
Ausnahme der letzten.f <- mkMatcher(pat)
erstes in istsequence
(denkenassembly line
) alles , was wir wollen aus er heraus zu nehmen istf
und es auf den nächsten Arbeiter in der Montagelinie passieren, wir den nächsten Arbeiter in unserer Montagelinie (die nächste Funktion) die Fähigkeit lassen , um festzustellen , was das sein würde , Verpackung zurück von unserem Artikel ist deshalb die letzte Funktionmap
.Der letzte
g <- mkMatcher(pat2)
wirdmap
dies verwenden, weil es das letzte am Fließband ist! so kann es nur die letzte Operation machen mitmap( g =>
der ja! zieht ausg
und verwendet das,f
was bereits aus dem Behälter herausgezogen wurde,flatMap
daher erhalten wir zuerst:mkMatcher (pat) flatMap (f // Funktion herausziehen f Element an nächsten Fließbandarbeiter weitergeben (Sie sehen, es hat Zugriff auf
f
und verpacken es nicht zurück Ich meine, lassen Sie die Karte die Verpackung bestimmen, lassen Sie den nächsten Fließbandarbeiter bestimmen container. mkMatcher (pat2) map (g => f (s) ...)) // da dies die letzte Funktion in der Montagelinie ist, werden wir map verwenden und g aus dem Container und zurück zur Verpackung ziehen , seinemap
und diese Verpackung werden den ganzen Weg drosseln und unser Paket oder unser Container sein, yah!quelle
Das Grundprinzip besteht darin, monadische Operationen zu verketten, was als Vorteil eine ordnungsgemäße "schnelle Fehlerbehandlung" bietet.
Es ist eigentlich ziemlich einfach. Die
mkMatcher
Methode gibt eineOption
(die eine Monade ist) zurück. Das ErgebnismkMatcher
der monadischen Operation ist entweder aNone
oder aSome(x)
.Das Anwenden der Funktion
map
oderflatMap
auf a gibtNone
immer a zurückNone
- die Funktion, die als Parameter an übergebenmap
undflatMap
nicht ausgewertet wird.Wenn in Ihrem Beispiel
mkMatcher(pat)
eine None zurückgegeben wird, gibt die darauf angewendete flatMap a zurückNone
(die zweite monadische OperationmkMatcher(pat2)
wird nicht ausgeführt), und die letzte Operationmap
gibt erneut a zurückNone
. Mit anderen Worten, wenn eine der Operationen zum Verständnis eine Keine zurückgibt, haben Sie ein ausfallsicheres Verhalten und der Rest der Operationen wird nicht ausgeführt.Dies ist der monadische Stil der Fehlerbehandlung. Der imperative Stil verwendet Ausnahmen, die im Grunde genommen Sprünge sind (zu einer catch-Klausel).
Ein letzter Hinweis: Die
patterns
Funktion ist eine typische Methode zum "Übersetzen" einer imperativen Stilfehlerbehandlung (try
...catch
) in eine monadische Stilfehlerbehandlung mitOption
quelle
flatMap
(und nichtmap
) verwendet wird, um den ersten und den zweiten Aufruf von "zu verketten"mkMatcher
, aber warummap
(und nichtflatMap
) verwendet wird, um den zweitenmkMatcher
und denyields
Block zu "verketten" ?flatMap
erwartet, dass Sie eine Funktion übergeben, die das in der Monade "eingewickelte" / angehobene Ergebnis zurückgibt, währendmap
Sie das Umhüllen / Anheben selbst durchführen. Während der Aufrufverkettung von Operationen in müssenfor comprehension
Sie,flatmap
damit die als Parameter übergebenen Funktionen zurückkehren könnenNone
(Sie können den Wert nicht in None setzen). Esyield
wird erwartet , dass der letzte Operationsaufruf, der im ausgeführt wird, ausgeführt wird und einen Wert zurückgibt. a,map
um die letzte Operation zu verketten, ist ausreichend und vermeidet, dass das Ergebnis der Funktion in die Monade gehoben werden muss.Dies kann wie folgt übersetzt werden:
Führen Sie dies aus, um eine bessere Übersicht über die Erweiterung zu erhalten
Ergebnisse sind:
Dies ähnelt einer
flatMap
Schleife durch jedes Element inpat
und leitet jedes Elementmap
an jedes Element in weiterpat2
quelle
Erstens
mkMatcher
gibt eine Funktion , deren Signatur istString => Boolean
, die eine reguläre Java - Prozedur ist , die gerade ausgeführt werdenPattern.compile(string)
, wie in der gezeigtepattern
Funktion. Dann schauen Sie sich diese Zeile anDie
map
Funktion wird auf das Ergebnis angewandtpattern
, das istOption[Pattern]
, so dass diep
inp => xxx
genau das Muster , das Sie zusammengestellt. Wenn ein Muster gegeben istp
, wird eine neue Funktion erstellt, die einen String akzeptierts
und prüft, ob sies
mit dem Muster übereinstimmt.Beachten Sie, dass die
p
Variable an das kompilierte Muster gebunden ist. Nun ist klar, wie eine Funktion mit Signatur aufgebautString => Boolean
istmkMatcher
.Als nächstes überprüfen wir die
bothMatch
Funktion, auf der basiertmkMatcher
. Um zu zeigen, wie esbothMathch
funktioniert, schauen wir uns zuerst diesen Teil an:Da wir eine Funktion mit Signatur
String => Boolean
von erhalten habenmkMatcher
, dieg
in diesem Zusammenhangg(s)
äquivalent zu istPattern.compile(pat2).macher(s).matches
, wird zurückgegeben, wenn der String mit dem Muster übereinstimmtpat2
. Wie wäref(s)
es also mitg(s)
dem einzigen Unterschied, dass der erste Aufruf dermkMatcher
VerwendungflatMap
stattmap
Warum? DamkMatcher(pat2) map (g => ....)
zurückgegeben wirdOption[Boolean]
, erhalten Sie ein verschachteltes Ergebnis,Option[Option[Boolean]]
wenn Siemap
beide Aufrufe verwenden. Dies ist nicht das, was Sie möchten.quelle