Respuestas:
Se usa en comprensiones de secuencia (como las listas de comprensión y generadores de Python, donde también puede usar yield
).
Se aplica en combinación con for
y escribe un nuevo elemento en la secuencia resultante.
Ejemplo simple (de scala-lang )
/** Turn command line arguments to uppercase */
object Main {
def main(args: Array[String]) {
val res = for (a <- args) yield a.toUpperCase
println("Arguments: " + res.toString)
}
}
La expresión correspondiente en F # sería
[ for a in args -> a.toUpperCase ]
o
from a in args select a.toUpperCase
en Linq.
Ruby yield
tiene un efecto diferente.
Creo que la respuesta aceptada es excelente, pero parece que muchas personas no han captado algunos puntos fundamentales.
Primero, las for
comprensiones de Scala son equivalentes a las de Haskelldo
notación , y no es más que un azúcar sintáctico para la composición de múltiples operaciones monádicas. Como esta declaración probablemente no ayudará a nadie que necesite ayuda, intentemos de nuevo ... :-)
La for
comprensión de Scala es azúcar sintáctica para la composición de múltiples operaciones con mapa flatMap
y filter
. O foreach
. Scala en realidad traduce una for
-expresión en llamadas a esos métodos, por lo que cualquier clase que los proporcione, o un subconjunto de ellos, puede usarse para comprender.
Primero, hablemos de las traducciones. Hay reglas muy simples:
Esta
for(x <- c1; y <- c2; z <-c3) {...}
se traduce a
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Esta
for(x <- c1; y <- c2; z <- c3) yield {...}
se traduce a
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
Esta
for(x <- c; if cond) yield {...}
se traduce en Scala 2.7 en
c.filter(x => cond).map(x => {...})
o, en Scala 2.8, en
c.withFilter(x => cond).map(x => {...})
con un retroceso al primero si el método withFilter
no está disponible pero lo filter
está. Consulte la sección a continuación para obtener más información al respecto.
Esta
for(x <- c; y = ...) yield {...}
se traduce a
c.map(x => (x, ...)).map((x,y) => {...})
Cuando observas for
comprensiones muy simples , las map
/ foreach
alternativas se ven, de hecho, mejores. Sin embargo, una vez que comience a componerlos, puede perderse fácilmente en paréntesis y niveles de anidación. Cuando eso sucede, las for
comprensiones suelen ser mucho más claras.
Mostraré un ejemplo simple y omitiré intencionalmente cualquier explicación. Puede decidir qué sintaxis fue más fácil de entender.
l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))
o
for {
sl <- l
el <- sl
if el > 0
} yield el.toString.length
withFilter
Scala 2.8 introdujo un método llamado withFilter
, cuya principal diferencia es que, en lugar de devolver una nueva colección filtrada, filtra bajo demanda. El filter
método tiene su comportamiento definido en función de la rigurosidad de la colección. Para entender esto mejor, echemos un vistazo a algunos Scala 2.7 con List
(estricto) y Stream
(no estricto):
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
La diferencia ocurre porque filter
se aplica inmediatamente con List
, devolviendo una lista de probabilidades, ya que found
es false
. Solo entonces foreach
se ejecuta, pero, en este momento, el cambio no found
tiene sentido, como filter
ya se ha ejecutado.
En el caso de Stream
, la condición no se aplica inmediatamente. En cambio, cuando cada elemento es solicitado por foreach
, filter
prueba la condición, lo que permite foreach
influir en ella found
. Solo para dejarlo en claro, aquí está el código equivalente de comprensión:
for (x <- List.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
for (x <- Stream.range(1, 10); if x % 2 == 1 && !found)
if (x == 5) found = true else println(x)
Esto causó muchos problemas, porque la gente esperaba if
que se considerara a pedido, en lugar de aplicarse de antemano a toda la colección.
Se presenta Scala 2.8 withFilter
, que siempre es no estricto, sin importar la rigurosidad de la colección. El siguiente ejemplo se muestra List
con ambos métodos en Scala 2.8:
scala> var found = false
found: Boolean = false
scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9
scala> found = false
found: Boolean = false
scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
Esto produce el resultado que la mayoría de la gente espera, sin cambiar cómo se filter
comporta. Como nota al margen, Range
se cambió de no estricto a estricto entre Scala 2.7 y Scala 2.8.
withFilter
se supone que no es estricto también, incluso para colecciones estrictas, lo que merece una explicación. Consideraré esto ...
for(x <- c; y <- x; z <-y) {...}
se traduce en c.foreach(x => x.foreach(y => y.foreach(z => {...})))
2. for(x <- c; y <- x; z <- y) yield {...}
se traduce enc.flatMap(x => x.flatMap(y => y.map(z => {...})))
for(x <- c; y = ...) yield {...}
realmente traducido c.map(x => (x, ...)).map((x,y) => {...})
? ¿Creo que está traducido c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})
o me falta algo?
Sí, como dijo Earwicker, es más o menos el equivalente a LINQ select
y tiene muy poco que ver con Ruby's y Python yield
. Básicamente, donde en C # escribirías
from ... select ???
en Scala tienes en su lugar
for ... yield ???
También es importante entender que las comprensiones for
no solo funcionan con secuencias, sino con cualquier tipo que defina ciertos métodos, como LINQ:
map
, permite for
-expresiones que consisten en un solo generador.flatMap
tan bien como map
, permite for
-expresiones que consisten en varios generadores.foreach
, permite for
-loops sin rendimiento (tanto con generadores simples como múltiples).filter
, permite for
expresiones -filter que comienzan con un if
en la for
expresión.A menos que obtenga una mejor respuesta de un usuario de Scala (que no soy), aquí tengo mi entendimiento.
Solo aparece como parte de una expresión que comienza for
, que establece cómo generar una nueva lista a partir de una lista existente.
Algo como:
var doubled = for (n <- original) yield n * 2
Entonces, hay un elemento de salida para cada entrada (aunque creo que hay una forma de soltar duplicados).
Esto es bastante diferente de las "continuaciones imperativas" habilitadas por el rendimiento en otros idiomas, donde proporciona una manera de generar una lista de cualquier longitud, a partir de algún código imperativo con casi cualquier estructura.
(Si está familiarizado con C #, está más cerca del select
operador de LINQ de lo que está yield return
).
La palabra clave yield
en Scala es simplemente azúcar sintáctico que puede reemplazarse fácilmente por un map
, como Daniel Sobral ya explicó en detalle.
Por otro lado, yield
es absolutamente engañoso si está buscando generadores (o continuaciones) similares a los de Python . Consulte este hilo SO para obtener más información: ¿Cuál es la forma preferida de implementar 'rendimiento' en Scala?
Considere lo siguiente para la comprensión
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
Puede ser útil leerlo en voz alta de la siguiente manera
" Para cada número entero i
, si es mayor que 3
, entonces produzca (produzca) i
y agréguelo a la lista A
".
En términos de notación matemática del generador de conjuntos , la comprensión anterior es análoga a
que puede leerse como
" Para cada entero , si es mayor que , entonces es un miembro del conjunto ".
o alternativamente como
" es el conjunto de todos los enteros , de modo que cada uno es mayor que ".
El rendimiento es similar al ciclo for que tiene un búfer que no podemos ver y para cada incremento, sigue agregando el siguiente elemento al búfer. Cuando el ciclo for termina de ejecutarse, devolverá la colección de todos los valores producidos. El rendimiento puede usarse como operadores aritméticos simples o incluso en combinación con matrices. Aquí hay dos ejemplos simples para su mejor comprensión.
scala>for (i <- 1 to 5) yield i * 3
res: scala.collection.immutable.IndexedSeq [Int] = Vector (3, 6, 9, 12, 15)
scala> val nums = Seq(1,2,3)
nums: Seq[Int] = List(1, 2, 3)
scala> val letters = Seq('a', 'b', 'c')
letters: Seq[Char] = List(a, b, c)
scala> val res = for {
| n <- nums
| c <- letters
| } yield (n, c)
res: Seq [(Int, Char)] = Lista ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))
¡¡Espero que esto ayude!!
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.filter(_ > 3).map(_ + 1)
println( res3 )
println( res4 )
Estas dos piezas de código son equivalentes.
val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
Estas dos piezas de código también son equivalentes.
El mapa es tan flexible como el rendimiento y viceversa.
el rendimiento es más flexible que map (), ver ejemplo a continuación
val aList = List( 1,2,3,4,5 )
val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.map( _+ 1 > 3 )
println( res3 )
println( res4 )
el rendimiento imprimirá resultados como: Lista (5, 6), que es bueno
mientras que map () devolverá resultados como: Lista (falso, falso, verdadero, verdadero, verdadero), que probablemente no sea lo que pretendes.