web-dev-qa-db-fra.com

Quel est le rendement de Scala?

Je comprends Ruby et le rendement de Python. Quel est le rendement de Scala?

307
Geo

Il est utilisé dans compréhensions de séquence (comme les listes-compréhensions et les générateurs de Python, où vous pouvez aussi utiliser yield).

Il est appliqué en combinaison avec for et écrit un nouvel élément dans la séquence résultante.

Exemple 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)
  }
}

L’expression correspondante en F # serait

[ for a in args -> a.toUpperCase ]

ou

from a in args select a.toUpperCase 

à Linq.

yield de Ruby a un effet différent.

204
Dario

Je pense que la réponse acceptée est excellente, mais il semble que beaucoup de gens n’ont pas compris certains points fondamentaux.

Premièrement, les interprétations for de Scala sont équivalentes à la notation do de Haskell et il ne s'agit que d'un sucre syntaxique pour la composition de plusieurs opérations monadiques. Comme cette déclaration n’aidera probablement personne qui a besoin d’aide, réessayons… :-)

La compréhension de for de Scala est un sucre syntaxique pour la composition de multiples opérations avec map, flatMap et filter. Ou foreach. Scala traduit en fait une expression for- en appels à ces méthodes, de sorte que toute classe qui les fournit, ou un sous-ensemble d'entre elles, peut être utilisée pour la compréhension.

Parlons d'abord des traductions. Il y a des règles très simples:

  1. Cette

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

    est traduit en

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

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

    est traduit en

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

    for(x <- c; if cond) yield {...}
    

    est traduit sur Scala 2.7 en

    c.filter(x => cond).map(x => {...})
    

    ou, sur Scala 2.8, dans

    c.withFilter(x => cond).map(x => {...})
    

    avec un repli dans l'ancien si la méthode withFilter n'est pas disponible mais filter l'est. Veuillez consulter la section ci-dessous pour plus d'informations à ce sujet.

  4. Cette

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

    est traduit en

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

Lorsque vous regardez des interprétations très simples de for, les alternatives de map/foreach semblent meilleures. Une fois que vous avez commencé à les composer, vous pouvez facilement vous perdre entre parenthèses et niveaux d'imbrication. Lorsque cela se produit, les interprétations for sont généralement beaucoup plus claires.

Je vais vous montrer un exemple simple et omettre intentionnellement toute explication. Vous pouvez choisir la syntaxe la plus facile à comprendre.

l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

ou

for {
  sl <- l
  el <- sl
  if el > 0
} yield el.toString.length

withFilter

Scala 2.8 a introduit une méthode appelée withFilter, dont la principale différence est qu'au lieu de renvoyer une nouvelle collection filtrée, elle filtre à la demande. Le comportement de la méthode filter est défini en fonction de la rigueur de la collection. Pour mieux comprendre cela, jetons un coup d'œil à quelques Scala 2.7 avec List (strict) et Stream (non strict):

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 différence se produit parce que filter est immédiatement appliqué avec List, ce qui retourne une liste de cotes - puisque found est false. Alors seulement foreach est exécuté, mais à ce moment-là, le changement de found n'a pas de sens, car filter a déjà été exécuté.

Dans le cas de Stream, la condition n'est pas appliquée immédiatement. Au lieu de cela, chaque élément étant demandé par foreach, filter teste la condition, ce qui permet à foreach de l’influencer par le biais de found. Pour que tout soit clair, voici le code équivalent pour la compréhension:

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)

Cela posait de nombreux problèmes, car les gens s’attendaient à ce que la if soit considérée à la demande, au lieu d’être appliquée à l’avance à l’ensemble de la collection.

Scala 2.8 introduit withFilter, qui est toujours non strict, quelle que soit la rigueur de la collection. L'exemple suivant montre List avec les deux méthodes sur 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

Ceci produit le résultat attendu par la plupart des gens, sans changer le comportement de filter. Notons que Range est passé de non-strict à strict entre Scala 2.7 et Scala 2.8.

808
Daniel C. Sobral

Oui, comme l'a dit Earwicker, c'est à peu près l'équivalent de select de LINQ et n'a que très peu à voir avec les _yield de Ruby's et de Python. En gros, où en C # vous écririez

from ... select ??? 

dans Scala vous avez plutôt

for ... yield ???

Il est également important de comprendre que for- compréhensions ne fonctionne pas uniquement avec des séquences, mais avec tout type définissant certaines méthodes, comme LINQ:

  • Si votre type définit uniquement map, il autorise for- expressions consistant en un seul générateur.
  • Si elle définit flatMap ainsi que map, elle autorise for- expressions composées de plusieurs générateurs.
  • S'il définit foreach, il autorise for- boucles sans rendement (avec un seul et plusieurs générateurs).
  • S'il définit filter, il autorise les expressions for- commençant par if dans l'expression for.
23
Alexey Romanov

À moins que vous obteniez une meilleure réponse d'un utilisateur Scala (que je ne suis pas), voici ce que je comprends.

Il apparaît uniquement dans le cadre d'une expression commençant par for, qui indique comment générer une nouvelle liste à partir d'une liste existante.

Quelque chose comme:

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

Il y a donc un élément de sortie pour chaque entrée (bien que je pense qu'il existe un moyen de supprimer les doublons).

Ceci est assez différent des "suites impératives" activées par le rendement dans d'autres langues, où il fournit un moyen de générer une liste de n'importe quelle longueur, à partir d'un code impératif avec presque n'importe quelle structure.

(Si vous êtes familier avec C #, il est plus proche de l'opérateur LINQselect que de yield return).

13
Daniel Earwicker

Le mot clé yield dans Scala est simplement un sucre syntaxique qui peut facilement être remplacé par un map, comme Daniel Sobral a déjà expliqué en détail.

D'autre part, yield est absolument trompeur si vous recherchez des générateurs (ou des suites) similaires à ceux de Python . Voir ce fil SO pour plus d'informations: Quel est le moyen préféré d'implémenter le "rendement" dans Scala?

12
Richard Gomes

Considérez ce qui suit pour la compréhension

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

Il peut être utile de le lire à voix haute comme suit

" Pour chaque entier i, if il est supérieur à 3, puis donne (produit) i et l'ajoute à la liste A."

En termes de mathématiques notation de constructeur , la compréhension ci-dessus est analogue à

set-notation

qui peut être lu comme

" Pour chaque entier i, si il est supérieur à 3, alors il est un membre de l'ensemble A. "

ou alternativement comme

"A est l'ensemble de tous les entiers i, de sorte que chaque i est supérieur à 3. "

10
Mario Galic

Le rendement est similaire à la boucle for qui a un tampon que nous ne pouvons pas voir et pour chaque incrément, il continue d’ajouter l’élément suivant au tampon. Lorsque la boucle for a fini de s'exécuter, elle renvoie la collection de toutes les valeurs renvoyées. Le rendement peut être utilisé comme simple opérateur arithmétique ou même en combinaison avec des tableaux. Voici deux exemples simples pour une meilleure compréhension

scala>for (i <- 1 to 5) yield i * 3

res: scala.collection.immutable.IndexedSeq [Int] = Vecteur (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)] = Liste ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))

J'espère que cela t'aides!!

2
Manasa Chada
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 )

Ces deux morceaux de code sont équivalents.

val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )

println( res3 ) 
println( res4 )

Ces deux morceaux de code sont également équivalents.

La carte est aussi flexible que le rendement et vice-versa.

0
dotnetN00b