comprehension - for scala
¿Cuál es el rendimiento de Scala? (9)
A menos que obtenga una mejor respuesta de un usuario de Scala (que no lo soy), este es mi entendimiento.
Solo aparece como parte de una expresión que comienza con for
, que indica cómo generar una nueva lista a partir de una lista existente.
Algo como:
var doubled = for (n <- original) yield n * 2
Así que hay un elemento de salida para cada entrada (aunque creo que hay una forma de eliminar duplicados).
Esto es bastante diferente de las "continuaciones imperativas" habilitadas por el 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 #, está más cerca LINQ''s operador select
LINQ''s que de la yield return
).
Entiendo el rendimiento de Ruby y Python. ¿Qué hace el rendimiento de Scala?
Considere lo siguiente for-comprehension
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 entero i
, si es mayor que 3
, ceda (produzca) i
y agréguelo a la lista A
".
En términos de notación matemática de constructores 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 , tal que cada es mayor que . "
Creo que la respuesta aceptada es excelente, pero parece que muchas personas no han logrado captar 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 probable que esta declaración no ayude a nadie que necesite ayuda, intentemos nuevamente ... :-)
Scala''s for
comprensions es azúcar sintáctica para la composición de múltiples operaciones con map, flatMap
y filter
. O foreach
. Scala en realidad convierte una expresión- for
expresión en llamadas a esos métodos, por lo que cualquier clase que los proporcione, o un subconjunto de ellos, se puede usar con las comprensiones.
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 en el anterior si el método
withFilter
no está disponible pero elfilter
está. Por favor, consulte la siguiente sección para obtener más información sobre esto.Esta
for(x <- c; y = ...) yield {...}
se traduce a
c.map(x => (x, ...)).map((x,y) => {...})
Cuando nos fijamos en aspectos muy simples for
comprender, las alternativas map
/ foreach
ven, de hecho, mejor. Sin embargo, una vez que comienzas a componerlas, puedes perderte fácilmente entre paréntesis y niveles de anidamiento. Cuando eso sucede, las comprensiones suelen ser mucho más claras.
Mostraré un ejemplo simple y omitiré intencionalmente cualquier explicación. Puedes 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 colección nueva y filtrada, filtra según la demanda. El método de filter
tiene su comportamiento definido basado en el rigor 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 sucede porque el filter
se aplica inmediatamente con la List
, devolviendo una lista de probabilidades, ya que se found
es false
. Solo entonces se ejecuta foreach
, pero, en este momento, el cambio found
tiene sentido, ya que el filter
ya se ha ejecutado.
En el caso de Stream
, la condición no se aplica de inmediato. En cambio, como cada elemento es solicitado por foreach
, el filter
prueba la condición, lo que le permite a cada persona influir en él a través de la found
. Solo para dejarlo claro, aquí está el código de comprensión equivalente:
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 que se considerara a pedido, en lugar de aplicarse a toda la colección de antemano.
Scala 2.8 se introdujo con withFilter
, que siempre es no estricto, sin importar el rigor de la colección. El siguiente ejemplo muestra la 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 comporta el filter
. Como nota al margen, el Range
se cambió de no estricto a estricto entre Scala 2.7 y Scala 2.8.
El rendimiento es similar al de un bucle que tiene un búfer que no podemos ver y para cada incremento, continúa agregando el siguiente elemento al búfer. Cuando el bucle for termine de ejecutarse, devolverá la colección de todos los valores producidos. El rendimiento se puede utilizar 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!!
Sí, como dijo Earwicker, es prácticamente equivalente al select
de LINQ y tiene muy poco que ver con el yield
Ruby y Python. Básicamente, donde en C # escribirías
from ... select ???
en Scala tienes en su lugar
for ... yield ???
También es importante entender que for
-comprensiones no solo funcionan con secuencias, sino con cualquier tipo que defina ciertos métodos, como LINQ:
- Si su tipo define solo el
map
, permite -expresiones consistentes en un solo generador. - Si define
flatMap
ymap
, permite -expresiones que constan de varios generadores. - Si define
foreach
, permite bucles sin rendimiento (con generadores únicos y múltiples). - Si define el
filter
, permite expresiones defilter
que comienzan conif
en la expresiónfor
.
el rendimiento es más flexible que el mapa (), vea el 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á un resultado como: Lista (5, 6), que es bueno
mientras que map () devolverá el resultado como: Lista (false, false, true, true, true), que probablemente no sea lo que pretendes.
El yield
palabra clave en Scala es simplemente azúcar sintáctico que puede ser fácilmente reemplazado por un map
, como Daniel Sobral ya explicó en detalle.
Por otro lado, el 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?
Se usa en la comprensión de secuencias (como las listas de comprensión y generadores de Python, donde también se puede usar el 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.
El yield
de Ruby tiene un efecto diferente.
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.