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]