TL; DR przejdź bezpośrednio do ostatniego przykładu
Spróbuję podsumować.
Definicje
forZrozumienie jest skrótem składnia łączyć flatMapi mapw sposób, który jest łatwy do odczytania i powodem około.
Uprośćmy trochę sprawę i załóżmy, że każdy, classktóry zapewnia obie powyższe metody, można nazwać a, monada użyjemy symbolu M[A]do oznaczenia a monadz typem wewnętrznym A.
Przykłady
Niektóre powszechnie spotykane monady to:
List[String] gdzie
M[X] = List[X]
A = String
Option[Int] gdzie
Future[String => Boolean] gdzie
M[X] = Future[X]
A = (String => Boolean)
map i flatMap
Zdefiniowany w ogólnej monadzie M[A]
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
na przykład
val list = List("neo", "smith", "trinity")
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)
do wyrażenia
Każda linia w wyrażeniu używającym <-symbolu jest tłumaczona na flatMapwywołanie, z wyjątkiem ostatniej linii, która jest tłumaczona na mapwywołanie końcowe , gdzie „symbol związany” po lewej stronie jest przekazywany jako parametr funkcji argumentu (co wcześniej dzwoniliśmy f: A => M[B]):
for {
bound <- list
out <- f(bound)
} yield out
list.flatMap { bound =>
f(bound).map { out =>
out
}
}
list.flatMap { bound =>
f(bound)
}
list flatMap f
Wyrażenie for z tylko jednym <-jest konwertowane na mapwywołanie z wyrażeniem przekazanym jako argument:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Teraz do rzeczy
Jak widać, mapoperacja zachowuje „kształt” oryginału monad, więc to samo dzieje się z yieldwyrażeniem: a Listpozostaje a Listz treścią przekształconą przez operację w yield.
Z drugiej strony każda linia oprawy forjest tylko kompozycją kolejnych monads, które muszą być „spłaszczone”, aby zachować jeden „kształt zewnętrzny”.
Załóżmy przez chwilę, że każde wewnętrzne powiązanie zostało przetłumaczone na mapwywołanie, ale prawa ręka była tą samą A => M[B]funkcją, skończysz z znakiem M[M[B]]dla każdego wiersza w zrozumieniu.
Celem całej forskładni jest łatwe „spłaszczenie” konkatenacji kolejnych operacji monadycznych (tj. Operacji, które „podnoszą” wartość w „kształt monadyczny”:) A => M[B], z dodaniem końcowej mapoperacji, która prawdopodobnie wykonuje przekształcenie końcowe.
Mam nadzieję, że wyjaśnia to logikę wyboru tłumaczenia, które jest stosowane w sposób mechaniczny, to znaczy: n flatMappołączenia zagnieżdżone zakończone pojedynczym mapwywołaniem.
Wymyślony przykład ilustrujący.
Ma na celu pokazanie ekspresji forskładni
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 (_ + _)
}
Czy potrafisz odgadnąć rodzaj valuesList?
Jak już powiedziano, kształt litery monadjest zachowywany podczas rozumienia, więc zaczynamy od Listin company.branchesi musimy kończyć na List.
Zamiast tego typ wewnętrzny zmienia się i jest określany przez yieldwyrażenie: który jestcustomer.value: Int
valueList powinien być List[Int]