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]
     
                
Lists. Simapduplicas una funciónA => List[B](que es una de las operaciones monádicas esenciales) sobre algún valor, terminas con una Lista [Lista [B]] (damos por sentado que los tipos coinciden). El bucle interno para comprensión compone esas funciones con laflatMapoperación correspondiente , "aplanar" la forma de Lista [Lista [B]] en una Lista [B] simple ... Espero que esto quede claroyieldcláusula escustomer.value, cuyo tipo esInt, por lo tanto, el conjunto sefor comprehensionevalúa como aList[Int].No soy una mega mente de Scala, así que siéntete libre de corregirme, ¡pero así es como me explico la
flatMap/map/for-comprehensionsaga!Para comprender
for comprehensiony su traducciónscala's map / flatMap, debemos dar pequeños pasos y comprender las partes que componen,mapyflatMap. Pero no esscala's flatMapsolomapconflattenle preguntas a ti mismo! Si es así, ¿por qué a tantos desarrolladores les resulta tan difícil entenderlo o comprenderlofor-comprehension / flatMap / map? Bueno, si solo mira scalamapy laflatMapfirma, verá que devuelven el mismo tipo de retornoM[B]y funcionan con el mismo argumento de entradaA(al menos la primera parte de la función que toman) si eso es así, ¿qué hace la diferencia?Nuestro plan
map.flatMap.for comprehension.`Mapa de Scala
firma del mapa scala:
map[B](f: (A) => B): M[B]Pero falta una gran parte cuando miramos esta firma, y es - ¿de dónde
Aviene esto ? nuestro contenedor es de tipo,Apor lo que es importante mirar esta función en el contexto del contenedor -M[A]. Nuestro contenedor podría ser unListelemento de tipoAy nuestramapfunción toma una función que transforma cada elemento de tipoAen tipoB, luego devuelve un contenedor de tipoB(oM[B])Escribamos la firma del mapa teniendo en cuenta el contenedor:
M[A]: // We are in M[A] context. map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]Tenga en cuenta un hecho extremadamente importante sobre el mapa : se agrupa automáticamente en el contenedor de salida,
M[B]no tiene control sobre él. Destaquémoslo de nuevo:mapelige el contenedor de salida para nosotros y será el mismo contenedor que la fuente en la que trabajamos, por lo que para elM[A]contenedor obtenemos el mismoMcontenedor solo paraBM[B]y nada más.maphace esta contenedorización para nosotros, simplemente le damos un mapeo deAaBy lo pondría en el cuadro de ¡M[B]lo pondrá en el cuadro por nosotros!Verá que no especificó cómo
containerizeel elemento que acaba de especificar cómo transformar los elementos internos. Y como tenemos el mismo contenedorMpara ambosM[A]yM[B]esto significa queM[B]es el mismo contenedor, lo que significa que si lo tieneList[A], tendrá unList[B]y, lo que es más importante, ¡maplo hará por usted!Ahora que hemos tratado
map, pasemos aflatMap.Mapa plano de Scala
Veamos su firma:
flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]Verá la gran diferencia de map a
flatMapen flatMap, le proporcionamos la función que no solo convierte,A to Bsino que también lo contiene en contenedoresM[B].¿Por qué nos importa quién realiza la contenedorización?
Entonces, ¿por qué nos preocupamos tanto de la función de entrada para map / flatMap en la contenedorización
M[B]o el mapa en sí hace la contenedorización por nosotros?Como ve, en el contexto de
for comprehensionlo que está sucediendo, hay múltiples transformaciones en el artículo proporcionado en el,forpor lo que le estamos dando al siguiente trabajador en nuestra línea de ensamblaje la capacidad de determinar el empaque. ¡Imagínese que tenemos una línea de ensamblaje cada trabajador hace algo con el producto y solo el último trabajador lo empaca en un contenedor! bienvenido aflatMapeste es su propósito, enmapcada trabajador cuando termina de trabajar en el artículo también lo empaqueta para que usted tenga contenedores sobre contenedores.El poderoso para la comprensión
Ahora analicemos su comprensión teniendo en cuenta lo que dijimos anteriormente:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for { f <- mkMatcher(pat) g <- mkMatcher(pat2) } yield f(s) && g(s)Qué tenemos aquí:
mkMatcherdevuelve uncontainerel contenedor contiene una función:String => Boolean<-que se traducen,flatMapexcepto el último.f <- mkMatcher(pat)es el primero ensequence(pensarassembly line) todo lo que queremos es tomarlofy pasarlo al siguiente trabajador en la línea de montaje, dejamos que el siguiente trabajador de nuestra línea de montaje (la siguiente función) tenga la capacidad de determinar cuál sería el embalaje de nuestro artículo por eso la última función esmap.¡El último
g <- mkMatcher(pat2)usarámapesto porque es el último en la línea de montaje! para que pueda hacer la operación final con lomap( g =>que sí! sacagy usa elfque ya ha sido sacado del contenedor por el,flatMappor lo tanto, terminamos con el primero:mkMatcher (pat) flatMap (f // extraer la función f entregar el artículo al siguiente trabajador de la línea de ensamblaje (ve que tiene acceso
fy no empaquetarlo de nuevo, es decir, dejar que el mapa determine el empaque dejar que el siguiente trabajador de la línea de ensamblaje determine container. mkMatcher (pat2) map (g => f (s) ...)) // como esta es la última función en la línea de ensamblaje, usaremos map y sacaremos g del contenedor y regresaremos al empaque , sumapy este empaque se acelerará por completo y será nuestro paquete o nuestro contenedor, ¡yah!fuente
La razón es encadenar operaciones monádicas, lo que proporciona como beneficio un manejo adecuado de errores "rápido de fallas".
En realidad, es bastante simple. El
mkMatchermétodo devuelve unOption(que es una mónada). El resultado demkMatcherla operación monádica es aNoneo aSome(x).La aplicación de la función
mapoflatMapa aNonesiempre devuelve aNone: la función pasada como parámetromapyflatMapno se evalúa.Por lo tanto, en su ejemplo, si
mkMatcher(pat)devuelve None, el flatMap que se le aplica devolverá aNone(la segunda operación monádicamkMatcher(pat2)no se ejecutará) y la finalmapvolverá a devolver aNone. En otras palabras, si alguna de las operaciones en la comprensión, devuelve Ninguno, tiene un comportamiento de falla rápida y el resto de las operaciones no se ejecutan.Este es el estilo monádico de manejo de errores. El estilo imperativo usa excepciones, que son básicamente saltos (a una cláusula de captura)
Una nota final: la
patternsfunción es una forma típica de "traducir" un manejo de errores de estilo imperativo (try...catch) a un manejo de errores de estilo monádico usandoOptionfuente
flatMap(y nomap) se usa para "concatenar" la primera y la segunda invocación demkMatcher, pero por quémap(y noflatMap) se usa "concatenar" la segundamkMatchery elyieldsbloque?flatMapespera que pase una función que devuelva el resultado "envuelto" / levantado en la Mónada, mientrasmapque él mismo hará el envasado / levantamiento. Durante el encadenamiento de llamadas de operaciones en elfor comprehension, debe hacerloflatmappara que las funciones pasadas como parámetro puedan regresarNone(no puede elevar el valor a Ninguno). Seyieldespera que la última llamada a la operación, la que está en el, se ejecute y devuelva un valor; amappara encadenar esa última operación es suficiente y evita tener que levantar el resultado de la función en la mónada.Esto se puede traducir como:
def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for { f <- mkMatcher(pat) // for every element from this [list, array,tuple] g <- mkMatcher(pat2) // iterate through every iteration of pat } yield f(s) && g(s)Ejecute esto para ver mejor cómo se expandió
def match items(pat:List[Int] ,pat2:List[Char]):Unit = for { f <- pat g <- pat2 } println(f +"->"+g) bothMatch( (1 to 9).toList, ('a' to 'i').toList)los resultados son:
1 -> a 1 -> b 1 -> c ... 2 -> a 2 -> b ...Esto es similar a
flatMap- recorrer cada elemento enpatymappara cada elemento a cada elemento enpat2fuente
Primero,
mkMatcherdevuelve una función cuya firma esString => Boolean, que es un procedimiento java normal que acaba de ejecutarsePattern.compile(string), como se muestra en lapatternfunción. Entonces, mira esta líneapattern(pat) map (p => (s:String) => p.matcher(s).matches)La
mapfunción se aplica al resultado depattern, que esOption[Pattern], por lo quepinp => xxxes solo el patrón que compiló. Entonces, dado un patrónp, se construye una nueva función, que toma una Cadenasy verifica siscoincide con el patrón.(s: String) => p.matcher(s).matchesTenga en cuenta que la
pvariable está limitada al patrón compilado. Ahora, está claro que cómoString => Booleanse construye una función con firmamkMatcher.A continuación, revisemos la
bothMatchfunción, que se basa enmkMatcher. Para mostrar cómobothMathchfunciona, primero miramos esta parte:Dado que obtuvimos una función con firma
String => BooleandemkMatcher, que estágen este contexto,g(s)es equivalente aPattern.compile(pat2).macher(s).matches, que devuelve si el String s coincide con el patrónpat2. Entonces, ¿qué talf(s), es lo mismo queg(s), la única diferencia es que, la primera llamada demkMatcherusosflatMap, en lugar demap, por qué? Debido a quemkMatcher(pat2) map (g => ....)devuelveOption[Boolean], obtendrá un resultado anidadoOption[Option[Boolean]]si lo usamappara ambas llamadas, eso no es lo que desea.fuente