TL; DR przejdź bezpośrednio do ostatniego przykładu
Spróbuję podsumować.
Definicje
for
Zrozumienie jest skrótem składnia łączyć flatMap
i map
w sposób, który jest łatwy do odczytania i powodem około.
Uprośćmy trochę sprawę i załóżmy, że każdy, class
który zapewnia obie powyższe metody, można nazwać a, monad
a użyjemy symbolu M[A]
do oznaczenia a monad
z 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 flatMap
wywołanie, z wyjątkiem ostatniej linii, która jest tłumaczona na map
wywoł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 map
wywoł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ć, map
operacja zachowuje „kształt” oryginału monad
, więc to samo dzieje się z yield
wyrażeniem: a List
pozostaje a List
z treścią przekształconą przez operację w yield
.
Z drugiej strony każda linia oprawy for
jest 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 map
wywoł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 for
skł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 map
operacji, 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
flatMap
połączenia zagnieżdżone zakończone pojedynczym map
wywołaniem.
Wymyślony przykład ilustrujący.
Ma na celu pokazanie ekspresji for
skł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 monad
jest zachowywany podczas rozumienia, więc zaczynamy od List
in company.branches
i musimy kończyć na List
.
Zamiast tego typ wewnętrzny zmienia się i jest określany przez yield
wyrażenie: który jestcustomer.value: Int
valueList
powinien być List[Int]