¿Cuál es el rendimiento de Scala?

Respuestas:

205

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 fory 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 yieldtiene un efecto diferente.

Darío
fuente
57
Entonces, ¿por qué usaría el rendimiento en lugar del mapa? Este código de mapa es equivalente val res = args.map (_. ToUpperCase), ¿verdad?
Geo
44
En caso de que te guste más la sintaxis. Además, como señala alexey, las comprensiones también proporcionan una buena sintaxis para acceder a flatMap, filter y foreach.
Nathan Shively-Sanders
22
Correcto. Si solo tiene un mapa simple, un generador con no if, ciertamente diría que el mapa de llamadas es más legible. Si tiene varios generadores dependiendo uno del otro, y / o filtros, puede preferir una expresión.
Alexey Romanov
13
Tenga en cuenta que el ejemplo dado no es equivalente a la expresión del mapa: es el mismo. A para la comprensión se traduce en llamadas a map, flatMap y filter.
Daniel C. Sobral
99
La respuesta comienza así: "Se usa en comprensiones de secuencia (como las listas de comprensión y generadores de Python, donde también se puede usar el rendimiento)". Esto lleva a pensar erróneamente que el rendimiento en Scala es similar al rendimiento en Python. Este no es el caso. En Python, el rendimiento se usa en el contexto de las rutinas (o continuaciones) mientras que no es el caso en Scala. Para obtener más aclaraciones, visite este hilo: stackoverflow.com/questions/2201882/…
Richard Gomes
817

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

Primero, las forcomprensiones 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 forcomprensión de Scala es azúcar sintáctica para la composición de múltiples operaciones con mapa flatMapy 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:

  1. Esta

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

    se traduce a

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

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

    se traduce a

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. 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 withFilterno está disponible pero lo filterestá. Consulte la sección a continuación para obtener más información al respecto.

  4. Esta

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

    se traduce a

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

Cuando observas forcomprensiones muy simples , las map/ foreachalternativas 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 forcomprensiones 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 filtermé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 filterse aplica inmediatamente con List, devolviendo una lista de probabilidades, ya que foundes false. Solo entonces foreachse ejecuta, pero, en este momento, el cambio no foundtiene sentido, como filterya se ha ejecutado.

En el caso de Stream, la condición no se aplica inmediatamente. En cambio, cuando cada elemento es solicitado por foreach, filterprueba la condición, lo que permite foreachinfluir 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 ifque 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 Listcon 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 filtercomporta. Como nota al margen, Rangese cambió de no estricto a estricto entre Scala 2.7 y Scala 2.8.

Daniel C. Sobral
fuente
2
Hay un nuevo método conFilter en scala 2.8. for (x <- c; if cond) yield {...} se traduce a c.withFilter (x => cond) .map (x => {...}) en scala2.8.
Eastsun
2
@Eastsun Lo suficientemente cierto, aunque también hay una recuperación automática. withFilterse supone que no es estricto también, incluso para colecciones estrictas, lo que merece una explicación. Consideraré esto ...
Daniel C. Sobral
2
@Daniel: Hay un gran tratamiento de este mismo tema en "Programación en Scala", por Odersky, et al. (Estoy seguro de que ya lo sabes). +1 por mostrarlo.
Ralph
Los primeros 2 puntos son correctos con: 1. 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 => {...})))
Dominik
¿Está esto 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?
prostynick
23

Sí, como dijo Earwicker, es más o menos el equivalente a LINQ selecty 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 forno solo funcionan con secuencias, sino con cualquier tipo que defina ciertos métodos, como LINQ:

  • Si su tipo define solo map, permite for-expresiones que consisten en un solo generador.
  • Si define flatMaptan bien como map, permite for-expresiones que consisten en varios generadores.
  • Si define foreach, permite for-loops sin rendimiento (tanto con generadores simples como múltiples).
  • Si define filter, permite forexpresiones -filter que comienzan con un if en la forexpresión.
Alexey Romanov
fuente
2
Enigma de @Eldritch: que curiosamente es el mismo orden en que se describen las especificaciones originales de SQL. En algún punto del camino, el lenguaje SQL invirtió el orden, pero tiene mucho sentido describir primero de lo que está sacando y luego lo que espera obtener de él.
Jordan Parmer
13

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

Daniel Earwicker
fuente
1
debería ser "var duplicado = para (n <- original) rendimiento n * 2".
Russel Yang el
11

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) iy 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

notación de conjunto

que puede leerse como

" Para cada entero yo, si es mayor que 3, entonces es un miembro del conjunto UNA".

o alternativamente como

" UNAes el conjunto de todos los enteros yo, de modo que cada uno yoes mayor que 3".

Mario Galic
fuente
2

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!!

Manasa Chada
fuente
Al responder una pregunta tan antigua (hace más de 9 años), es útil señalar cómo su respuesta es diferente de todas las otras respuestas ya enviadas.
jwvh
Pensé que aclarar la duda es importante y no dar una respuesta diferente, ya que incluso yo también soy un principiante que está aprendiendo este idioma. Gracias por la sugerencia.
Manasa Chada
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.

dotnetN00b
fuente
-3

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.

Michael Peng
fuente
44
Esa comparación está mal. Estás comparando dos cosas diferentes. La expresión en rendimiento de ninguna manera está haciendo lo mismo que la expresión en el mapa. Además, no muestra la "flexibilidad" del rendimiento en comparación con el mapa en absoluto.
dotnetN00b