2009-06-27 10 views

Respuesta

178

Se usa en sequence comprehensions (como la lista de comprensiones 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's yield tiene un efecto diferente.

+49

¿Por qué usaría yield en lugar del mapa? Este código de mapa es equivalente val res = args.map (_. ToUpperCase), ¿verdad? – Geo

+4

En caso de que prefiera la sintaxis. Además, como señala alexey, las comprensiones también proporcionan una sintaxis agradable para acceder a flatMap, filter y foreach. –

+2

Preferiría mucho la sintaxis args map {_ toUpperCase} personalmente ya que "se siente" mucho más OO. Parece más en consonancia con el objetivo de Scala de ofrecer a través de la biblioteca lo que solo está disponible en otros idiomas a través de construcciones personalizadas y palabras clave –

10

A menos que obtenga una mejor respuesta de un usuario de Scala (que no soy), aquí está mi entendimiento.

Aparece solo como parte de una expresión que comienza con for, que indica cómo generar una nueva lista de una lista existente.

Algo así como:

var doubled = for (n <- original) yield n * 2 

Siempre hay un elemento de salida para cada entrada (aunque creo que hay una manera de dejar caer los duplicados).

Esto es bastante diferente de las "continuaciones imperativas" habilitadas por rendimiento en otros idiomas, donde proporciona una forma de generar una lista de cualquier longitud, desde algún código imperativo con casi cualquier estructura.

(Si está familiarizado con C#, se acerca más al operador LINQ'sselect que a).

+1

debe ser "var doubled = for (n <- original) yield n * 2 ". –

+0

Este es el que puedo entender fácilmente y con claridad. –

21

Sí, como dijo Earwicker, es prácticamente equivalente al select de LINQ y tiene muy poco que ver con el yield de Ruby y Python. Básicamente, donde en C# que iba a escribir

from ... select ??? 

en Scala tiene lugar

for ... yield ??? 

También es importante entender que for -comprehensions no sólo trabajan con secuencias, pero con cualquier tipo que define ciertos métodos, al igual que LINQ:

  • Si su tipo define simplemente map, permite for -expresiones que consiste en un generador individual.
  • Si define flatMap así como map, permite for -expresiones que consisten en de varios generadores.
  • Si define foreach, permite for bucles sin rendimiento (ambos con generadores únicos y múltiples).
  • Si define filter, permite for -filtrar expresiones comenzando con if en la expresión for.
+1

en el LINQ de C#, el orden correcto es" de ... seleccionar ??? " –

+2

@Eldritch acertijo - que curiosamente es el mismo orden en el que el Esquemas de especificaciones de SQL originales. En algún punto del camino, el lenguaje SQL invirtió el orden, pero tiene todo el sentido describir primero de qué se está extrayendo seguido de lo que se espera obtener de él. –

758

Creo que la respuesta aceptada es genial, pero parece que muchas personas no han captado algunos puntos fundamentales.

En primer lugar, las "comprensiones" de Scala son equivalentes a la notación "do" de Haskell, y no es más que un azúcar sintáctico para la composición de múltiples operaciones monádicas. Como es muy probable que esta afirmación no ayude a nadie que necesite ayuda, intentémoslo de nuevo ... :-)

El término "para comprender" de Scala es azúcar sintáctico para la composición de operaciones múltiples con mapa, mapa plano y filtro. O foreach. Scala realmente traduce una expresión para en llamadas a esos métodos, por lo que cualquier clase que los proporcione, o un subconjunto de ellos, se puede usar para las comprensiones.

Primero, hablemos de las traducciones. Hay reglas muy simples:

1) Este

for(x <- c1; y <- c2; z <-c3) {...} 

se traduce en

c1.foreach(x => c2.foreach(y => c3.foreach(z => {...}))) 

2) Este

for(x <- c1; y <- c2; z <- c3) yield {...} 

se traduce en

c1.flatMap(x => c2.flatMap(y => c3.map(z => {...}))) 

3) Este

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 repliegue en el anterior si el método withFilter es no disponible, pero filter es. Consulte la edición a continuación para obtener más información sobre esto.

4) Este

for(x <- c; y = ...) yield {...} 

se traduce en

c.map(x => (x, ...)).map((x,y) => {...}) 

Cuando nos fijamos en muy simple para comprensiones, las alternativas mapa/foreach se ven, de hecho, mejor. Sin embargo, una vez que comienzas a componerlos, puedes perderlos fácilmente entre paréntesis y niveles de anidación. Cuando eso sucede, las comprensiones suelen ser mucho más claras.

Mostraré un ejemplo simple e intencionalmente omito cualquier explicación. Puedes decidir qué sintaxis es 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 

EDITAR

Scala 2.8 introdujo un método llamado withFilter, cuya principal diferencia es que, en lugar de devolver una nueva, se filtró, colección, se filtra situ demanda. El método filter tiene su comportamiento definido en función de la rigurosidad de la colección. Para entender esto mejor, vamos a echar un vistazo a algunas 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 se debe a que el filtro se aplica inmediatamente con List, devolviendo una lista de probabilidades - - desde found es false. Solo entonces se ejecuta foreach, pero, en este momento, cambiar found no tiene sentido, ya que filter ya se ha ejecutado. En el caso de Stream, la condición no se aplica inmediatamente. En cambio, como cada elemento es solicitado por foreach, filter prueba la condición, lo que permite que foreach lo influencie a través de found. Sólo para que quede claro, aquí está el código equivalente para la 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 espera que el if para ser considerado bajo demanda, en lugar de ser aplicado a toda la colección de antemano.

Scala 2.8 introducido withFilter, que es siempre no estricto, sin importar la rigurosidad de la colección. El siguiente ejemplo 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 mayoría de las personas esperan, sin cambiar la forma como se comporta filter. Como nota al margen, Range fue cambiado de no estricto a estricto entre Scala 2.7 y Scala 2.8.

+2

Hay un nuevo método con Filter en scala 2.8. for (x <- c; if cond) yield {...} se traduce a c.withFilter (x => cond) .map (x => {...}) en scala2.8. – Eastsun

+1

@Eastsun Es cierto, aunque también hay un repliegue automático. 'withFilter' se supone que no es estricta también, incluso para colecciones estrictas, lo que merece alguna explicación. Consideraré esto ... –

+35

Una de las mejores respuestas que he visto en SO. +1 – Allyn

-2

rendimiento es más flexible que el mapa(), consulte el ejemplo siguiente

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) 

rendimiento imprimirá resultado como: Lista (5, 6), que es bueno

mientras que map() arrojará resultados como: Lista (falso, falso, verdadero, verdadero, verdadero), que probablemente no sea lo que usted desea.

+4

Esa comparación es incorrecta. Estás comparando dos cosas diferentes. La expresión en rendimiento no hace de ninguna manera lo mismo que la expresión en el mapa. Además, no muestra la "flexibilidad" de rendimiento en comparación con el mapa en absoluto. – dotnetN00b

+0

yield al + 1> 3 harían el mismo truco – philix

0
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.

4

Considere lo siguiente for-comprehension

val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i 

Puede ser útil para leer en voz alta la siguiente manera

"Para cada entero i, si es mayor que 3, entonces ceda (producir) i y agréguelo a la lista A. "

En términos de matemática set-builder notation, lo anterior para-comprensión es análoga a

set-notation

que puede ser leído como

"Para cada entero i, si se es mayor que 3, luego es un miembro del conjunto A. "

o alternativamente como

"A es el conjunto de todos los enteros i, de manera que cada i es mayor que 3."

Cuestiones relacionadas