TL; DR passe directement à l'exemple final
Je vais essayer de récapituler.
Définitions
La for
compréhension est un raccourci syntaxique à combiner flatMap
et map
d'une manière facile à lire et à raisonner.
Simplifions un peu les choses et supposons que tout ce class
qui fournit les deux méthodes susmentionnées peut être appelé a monad
et nous utiliserons le symbole M[A]
pour signifier a monad
avec un type interne A
.
Exemples
Certaines monades couramment observées comprennent:
List[String]
où
M[X] = List[X]
A = String
Option[Int]
où
Future[String => Boolean]
où
M[X] = Future[X]
A = (String => Boolean)
map et flatMap
Défini dans une monade générique 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]
par exemple
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)
pour l'expression
Chaque ligne de l'expression utilisant le <-
symbole est traduite en flatMap
appel, à l'exception de la dernière ligne qui est traduite en map
appel final , où le "symbole lié" sur le côté gauche est passé comme paramètre à la fonction d'argument (quel nous avons précédemment appelé 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
Une expression for avec un seul <-
est convertie en map
appel avec l'expression passée en argument:
// 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
Maintenant au point
Comme vous pouvez le voir, l' map
opération préserve la "forme" de l'original monad
, il en va de même pour l' yield
expression: a List
reste a List
avec le contenu transformé par l'opération dans le yield
.
D'autre part, chaque ligne de reliure dans le for
est juste une composition successive monads
, qui doit être "aplatie" pour conserver une seule "forme externe".
Supposons un instant que chaque liaison interne soit traduite en map
appel, mais que la main droite ait la même A => M[B]
fonction, vous vous retrouveriez avec un M[M[B]]
pour chaque ligne dans la compréhension.
L'intention de toute la for
syntaxe est de facilement "aplatir" la concaténation d'opérations monadiques successives (c'est-à-dire des opérations qui "soulèvent" une valeur dans une "forme monadique" :) A => M[B]
, avec l'ajout d'une map
opération finale qui effectue éventuellement une transformation finale.
J'espère que cela explique la logique du choix de la traduction, qui est appliquée de manière mécanique, c'est-à-dire: n
flatMap
des appels imbriqués conclus par un seul map
appel.
Un exemple illustratif artificiel
destiné à montrer l'expressivité de la for
syntaxe
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 (_ + _)
}
Pouvez-vous deviner le type de valuesList
?
Comme déjà dit, la forme du monad
est maintenue à travers la compréhension, donc nous commençons par un List
in company.branches
, et devons finir par un List
.
Le type interne change à la place et est déterminé par l' yield
expression: qui estcustomer.value: Int
valueList
devrait être un List[Int]
Lists
. Si vousmap
doublez une fonctionA => List[B]
(qui est l'une des opérations monadiques essentielles) sur une valeur, vous vous retrouvez avec une List [List [B]] (nous prenons pour acquis que les types correspondent). La boucle interne pour la compréhension compose ces fonctions avec l'flatMap
opération correspondante , "aplatir" la forme Liste [Liste [B]] en une simple Liste [B] ... J'espère que c'est clairyield
clause estcustomer.value
, dont le type estInt
, donc l'ensemblefor comprehension
s'évalue à aList[Int]
.Je ne suis pas un méga esprit scala alors n'hésitez pas à me corriger, mais c'est ainsi que je m'explique la
flatMap/map/for-comprehension
saga!Pour comprendre
for comprehension
et sa traduction,scala's map / flatMap
nous devons faire de petits pas et comprendre les parties qui composent -map
etflatMap
. Mais ce n'est passcala's flatMap
seulementmap
avecflatten
vous demandez-vous! si tel est le cas, pourquoi tant de développeurs ont-ils tant de mal à comprendre ou à comprendrefor-comprehension / flatMap / map
. Eh bien, si vous regardez simplement les scalamap
et laflatMap
signature, vous voyez qu'ils renvoient le même type de retourM[B]
et qu'ils fonctionnent sur le même argument d'entréeA
(au moins la première partie de la fonction qu'ils prennent) si c'est le cas, qu'est-ce qui fait la différence?Notre plan
map
.flatMap
.for comprehension
»Carte de Scala
signature de la carte scala:
Mais il manque une grande partie quand on regarde cette signature, et c'est - d'où cela
A
vient-il? notre conteneur est de type,A
il est donc important de regarder cette fonction dans le contexte du conteneur -M[A]
. Notre conteneur pourrait être unList
élément de typeA
et notremap
fonction prend une fonction qui transforme chaque élément de typeA
en typeB
, puis elle retourne un conteneur de typeB
(ouM[B]
)Écrivons la signature de la carte en tenant compte du conteneur:
Notez un fait extrêmement important à propos de la carte : elle se regroupe automatiquement dans le conteneur de sortie,
M[B]
vous n'avez aucun contrôle dessus. Soulignons encore une fois:map
choisit le conteneur de sortie pour nous et ce sera le même conteneur que la source sur laquelle nous travaillons, donc pour leM[A]
conteneur, nous obtenons le mêmeM
conteneur uniquement pourB
M[B]
et rien d'autre!map
fait cette conteneurisation pour nous, nous donnons juste un mappage deA
àB
et il le mettrait dans la boîte deM[B]
le mettra dans la boîte pour nous!Vous voyez que vous n'avez pas spécifié comment
containerize
l'élément que vous venez de spécifier comment transformer les éléments internes. Et comme nous avons le même conteneurM
pour les deuxM[A]
et queM[B]
cela signifie queM[B]
c'est le même conteneur, ce qui signifie que si vous en avez,List[A]
vous allez en avoir unList[B]
et, plus important encore, lemap
faire pour vous!Maintenant que nous avons traité
map
, passons àflatMap
.Plan d'appartement de Scala
Voyons sa signature:
Vous voyez la grande différence entre map et
flatMap
flatMap, nous lui fournissons la fonction qui ne se contente pas de la convertir,A to B
mais également de la conteneuriserM[B]
.pourquoi nous soucions-nous de qui fait la conteneurisation?
Alors pourquoi nous soucions-nous autant de la fonction d'entrée de map / flatMap la conteneurisation dans
M[B]
ou la carte elle-même fait la conteneurisation pour nous?Vous voyez que dans le contexte de
for comprehension
ce qui se passe, il y a plusieurs transformations sur l'article fourni dans lefor
. Nous donnons donc au prochain travailleur de notre chaîne de montage la possibilité de déterminer l'emballage. Imaginez que nous ayons une chaîne de montage, chaque travailleur fait quelque chose sur le produit et que seul le dernier travailleur l'emballe dans un conteneur! bienvenue àflatMap
ceci est son but, dansmap
chaque ouvrier une fois fini de travailler sur l'élément, le conditionne également afin que vous ayez des conteneurs sur les conteneurs.Le puissant pour la compréhension
Examinons maintenant votre compréhension en tenant compte de ce que nous avons dit ci-dessus:
Qu'avons-nous ici:
mkMatcher
retourne acontainer
le conteneur contient une fonction:String => Boolean
<-
elles se traduisent à l'flatMap
exception du dernier.f <- mkMatcher(pat)
c'est d'abordsequence
(penserassembly line
) tout ce que nous voulons en sortir est de le prendref
et de le transmettre au prochain ouvrier de la chaîne de montage, nous laissons au prochain ouvrier de notre chaîne de montage (la fonction suivante) la possibilité de déterminer ce que serait le emballage arrière de notre article c'est pourquoi la dernière fonction estmap
.Le dernier
g <- mkMatcher(pat2)
utiliseramap
ceci parce que son dernier en ligne d'assemblage! donc il peut simplement faire l'opération finale avecmap( g =>
laquelle oui! sortg
et utilise lef
qui a déjà été retiré du conteneur par leflatMap
donc nous nous retrouvons avec en premier:mkMatcher (pat) flatMap (f // fonction d'extraction f donne un article au prochain ouvrier de la chaîne d'assemblage (vous voyez qu'il a accès à
f
, et ne le remballe pas, je veux dire, laissez la carte déterminer l'emballage, laissez le prochain ouvrier de la chaîne d'assemblage déterminer le container. mkMatcher (pat2) map (g => f (s) ...)) // comme il s'agit de la dernière fonction de la chaîne de montage, nous allons utiliser map et retirer g du conteneur et le remettre dans l'emballage , sonmap
et cet emballage ralentiront complètement et seront notre colis ou notre conteneur, yah!la source
La logique est d'enchaîner les opérations monadiques, ce qui offre comme avantage une gestion correcte des erreurs "échouez rapidement".
C'est en fait assez simple. La
mkMatcher
méthode renvoie unOption
(qui est une Monade). Le résultat demkMatcher
, l'opération monadique, est soit a,None
soit aSome(x)
.L'application de la fonction
map
ouflatMap
à aNone
renvoie toujours aNone
- la fonction passée en paramètre àmap
etflatMap
n'est pas évaluée.Par conséquent, dans votre exemple, si
mkMatcher(pat)
renvoie un None, le flatMap qui lui est appliqué renverra aNone
(la deuxième opération monadiquemkMatcher(pat2)
ne sera pas exécutée) et la finalemap
renverra à nouveau aNone
. En d'autres termes, si l'une des opérations de la compréhension for, renvoie un None, vous avez un comportement d'échec rapide et le reste des opérations ne sont pas exécutées.C'est le style monadique de gestion des erreurs. Le style impératif utilise des exceptions, qui sont essentiellement des sauts (vers une clause catch)
Une dernière remarque: la
patterns
fonction est un moyen typique de "traduire" une gestion d'erreur de style impérative (try
...catch
) en une gestion d'erreur de style monadique utilisantOption
la source
flatMap
(et nonmap
) est utilisé pour "concaténer" le premier et le second appel demkMatcher
, mais pourquoimap
(et nonflatMap
) est utilisé "concaténer" le secondmkMatcher
et leyields
bloc?flatMap
s'attend à ce que vous passiez une fonction retournant le résultat «enveloppé» / soulevé dans la Monade, tandis que vousmap
effectuerez l'enrubannage / levage lui-même. Pendant le chaînage des appels des opérations dans le,for comprehension
vous devez le faireflatmap
pour que les fonctions passées en paramètre puissent être renvoyéesNone
(vous ne pouvez pas élever la valeur en None). Le dernier appel d'opération, celui de layield
est censé s'exécuter et renvoyer une valeur; amap
pour enchaîner cette dernière opération suffit et évite d'avoir à soulever le résultat de la fonction dans la monade.Cela peut être traduit comme:
Exécutez ceci pour une meilleure vue de la façon dont il est développé
les résultats sont:
Ceci est similaire à
flatMap
- boucle à travers chaque élément danspat
etmap
pour chaque élément à chaque élément danspat2
la source
Tout d'abord,
mkMatcher
retourne une fonction dont la signature estString => Boolean
, c'est une procédure Java régulière qui vient de s'exécuterPattern.compile(string)
, comme indiqué dans lapattern
fonction. Ensuite, regardez cette ligneLa
map
fonction est appliquée au résultat depattern
, ce quiOption[Pattern]
signifie que lep
inp => xxx
est simplement le modèle que vous avez compilé. Ainsi, étant donné un modèlep
, une nouvelle fonction est construite, qui prend une chaînes
et vérifie si elles
correspond au modèle.Notez que la
p
variable est liée au modèle compilé. Maintenant, il est clair que la façon dont une fonction avec signatureString => Boolean
est construite parmkMatcher
.Ensuite, vérifions la
bothMatch
fonction, qui est basée surmkMatcher
. Pour montrer commentbothMathch
fonctionne, nous regardons d'abord cette partie:Puisque nous avons une fonction avec la signature
String => Boolean
demkMatcher
, qui estg
dans ce contexte,g(s)
équivaut àPattern.compile(pat2).macher(s).matches
, qui renvoie si la chaîne correspond au modèlepat2
. Alors que diriez-vousf(s)
, c'est la même chose queg(s)
, la seule différence est que, le premier appel auxmkMatcher
utilisationsflatMap
, au lieu demap
, pourquoi? Parce quemkMatcher(pat2) map (g => ....)
renvoieOption[Boolean]
, vous obtiendrez un résultat imbriquéOption[Option[Boolean]]
si vous utilisezmap
pour les deux appels, ce n'est pas ce que vous voulez.la source