TL; DR ir directamente al ejemplo final
Intentaré recapitular.
Definiciones
La forcomprensión es un atajo de sintaxis para combinar flatMapy mapde una manera que es fácil de leer y razonar.
Simplifiquemos un poco las cosas y supongamos que cada classque proporciona los dos métodos antes mencionados se puede llamar a monady usaremos el símbolo M[A]para significar a monadcon un tipo interno A.
Ejemplos
Algunas mónadas que se ven comúnmente incluyen:
List[String] dónde
M[X] = List[X]
A = String
Option[Int] dónde
Future[String => Boolean] dónde
M[X] = Future[X]
A = (String => Boolean)
mapa y plano
Definido en una mónada genérica M[A]
def map(f: A => B): M[B]
def flatMap(f: A => M[B]): M[B]
p.ej
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)
para expresarse
Cada línea de la expresión que usa el <-símbolo se traduce a una flatMapllamada, excepto la última línea que se traduce a una mapllamada final , donde el "símbolo enlazado" en el lado izquierdo se pasa como parámetro a la función del argumento (lo que llamamos anteriormente 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
Una expresión for con solo uno <-se convierte en una mapllamada con la expresión pasada como argumento:
for {
bound <- list
} yield f(bound)
list.map { bound =>
f(bound)
}
list map f
Ahora al grano
Como puede ver, la mapoperación conserva la "forma" del original monad, por lo que lo mismo ocurre con la yieldexpresión: a Listqueda a Listcon el contenido transformado por la operación en yield.
Por otro lado, cada línea de encuadernación en el fores solo una composición de sucesivas monads, que deben ser "aplanadas" para mantener una única "forma externa".
Suponga por un momento que cada enlace interno se traduce en una mapllamada, pero la mano derecha tiene la misma A => M[B]función, terminaría con una M[M[B]]para cada línea de la comprensión.
La intención de toda la forsintaxis es "aplanar" fácilmente la concatenación de operaciones monádicas sucesivas (es decir, operaciones que "levantan" un valor en una "forma monádica":) A => M[B], con la adición de una mapoperación final que posiblemente realice una transformación final.
Espero que esto explique la lógica detrás de la elección de la traducción, que se aplica de forma mecánica, es decir: n flatMapllamadas anidadas concluidas por una sola mapllamada.
Un ejemplo ilustrativo elaborado con la
intención de mostrar la expresividad de la forsintaxis
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 (_ + _)
}
¿Puedes adivinar el tipo de valuesList?
Como ya se dijo, la forma del monadse mantiene a través de la comprensión, por lo que comenzamos con un Listin company.branchesy debemos terminar con un List.
En cambio, el tipo interno cambia y está determinado por la yieldexpresión: que escustomer.value: Int
valueList debería ser un List[Int]