for comprehension scala functional-programming yield

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:

  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 en el anterior si el método withFilter no está disponible pero el filter está. Por favor, consulte la siguiente sección para obtener más información sobre esto.

  4. 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 y map , 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 de filter que comienzan con if en la expresión for .

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.