¿Cuál es el rendimiento de Scala?
Entiendo el rendimiento de Ruby y Python. ¿Qué hace el rendimiento de Scala?
Creo que la respuesta aceptada es excelente, pero parece que muchas personas no han logrado comprender algunos puntos fundamentales.
Primero, for
las comprensiones de Scala son equivalentes a do
la notación de Haskell, y no es más que un azúcar sintáctico para la composición de múltiples operaciones monádicas. Como lo más probable es que esta afirmación no ayude a nadie que necesite ayuda, intentémoslo de nuevo… :-)
Las comprensiones de Scala for
son azúcar sintáctica para la composición de múltiples operaciones con map 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 comprensión.
Primero, hablemos de las traducciones. Hay reglas muy simples:
Este
for(x <- c1; y <- c2; z <-c3) {...}
se traduce en
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Este
for(x <- c1; y <- c2; z <- c3) yield {...}
se traduce en
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
Este
for(x <- c; if cond) yield {...}
se traduce en Scala 2.7 a
c.filter(x => cond).map(x => {...})
o, en Scala 2.8, en
c.withFilter(x => cond).map(x => {...})
con un recurso alternativo al primero si el método
withFilter
no está disponible pero sífilter
lo está. Consulte la sección siguiente para obtener más información al respecto.Este
for(x <- c; y = ...) yield {...}
se traduce en
c.map(x => (x, ...)).map((x,y) => {...})
Cuando nos fijamos en for
comprensiones muy simples, las alternativas map
/ foreach
parecen, de hecho, mejores. Sin embargo, una vez que comienzas a componerlos, puedes perderte fácilmente entre paréntesis y niveles de anidamiento. Cuando eso sucede, for
las 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 colección nueva filtrada, filtra según demanda. El filter
método tiene su comportamiento definido en función del rigor de la colección. Para entender esto mejor, echemos un vistazo a 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
. Sólo entonces foreach
se ejecuta, pero, en ese momento, el cambio found
no tiene sentido, como filter
ya se ha ejecutado.
En el caso de Stream
, la condición no se aplica inmediatamente. En cambio, a medida que cada elemento es solicitado por foreach
, filter
prueba la condición, lo que permite foreach
influir en él a través de found
. Para que quede 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 if
se considerara bajo demanda, en lugar de aplicarse a toda la colección de antemano.
Se introdujo Scala 2.8 withFilter
, que siempre no es estricto, sin importar el rigor de la colección. El siguiente ejemplo muestra List
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 su filter
comportamiento. Como nota al margen, Range
se cambió de no estricto a estricto entre Scala 2.7 y Scala 2.8.
Se usa en comprensión de secuencias (como las listas de comprensión y generadores de Python, donde también puede usarlo yield
).
Se aplica en combinación con for
un nuevo elemento y lo escribe 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.
Sí, como dijo Earwicker, es prácticamente equivalente a LINQ select
y tiene muy poco que ver con Ruby y Python yield
. Básicamente, ¿dónde en C# escribirías?
from ... select ???
en Scala tienes en su lugar
for ... yield ???
También es importante entender que for
las comprensiones no sólo funcionan con secuencias, sino con cualquier tipo que defina ciertos métodos, al igual que LINQ:
- Si su tipo define just
map
, permitefor
expresiones que constan de un único generador. - Si define
flatMap
además demap
, permitefor
expresiones que constan de varios generadores. - Si define
foreach
, permitefor
bucles sin rendimiento (tanto con generadores únicos como múltiples). - Si define
filter
, permitefor
filtrar expresiones que comiencen con anif
en lafor
expresión.