TL; DR ir directamente al ejemplo final
Intentaré recapitular.
Definiciones
La for
comprensión es un atajo de sintaxis para combinar flatMap
y map
de una manera que es fácil de leer y razonar.
Simplifiquemos un poco las cosas y supongamos que cada class
que proporciona los dos métodos antes mencionados se puede llamar a monad
y usaremos el símbolo M[A]
para significar a monad
con 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 flatMap
llamada, excepto la última línea que se traduce a una map
llamada 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 map
llamada 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 map
operación conserva la "forma" del original monad
, por lo que lo mismo ocurre con la yield
expresión: a List
queda a List
con el contenido transformado por la operación en yield
.
Por otro lado, cada línea de encuadernación en el for
es 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 map
llamada, 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 for
sintaxis 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 map
operació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
flatMap
llamadas anidadas concluidas por una sola map
llamada.
Un ejemplo ilustrativo elaborado con la
intención de mostrar la expresividad de la for
sintaxis
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 monad
se mantiene a través de la comprensión, por lo que comenzamos con un List
in company.branches
y debemos terminar con un List
.
En cambio, el tipo interno cambia y está determinado por la yield
expresión: que escustomer.value: Int
valueList
debería ser un List[Int]