Quel est le rendement de Scala?

Réponses:

205

Il est utilisé dans les compréhensions de séquences (comme les compréhensions de liste et les générateurs de Python, où vous pouvez yieldégalement les utiliser ).

Il est appliqué en combinaison avec foret é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.

Ruby yielda un effet différent.

Dario
la source
57
Alors pourquoi utiliser le rendement au lieu de la carte? Ce code de carte est équivalent à val res = args.map (_. ToUpperCase), non?
Geo
4
Si vous préférez la syntaxe. De plus, comme le souligne alexey, les compréhensions fournissent également une syntaxe intéressante pour accéder à flatMap, filter et foreach.
Nathan Shively-Sanders
22
Droite. Si vous avez juste une carte simple - un générateur sans if - je dirais certainement que la carte d'appel est plus lisible. Si vous avez plusieurs générateurs qui dépendent les uns des autres et / ou des filtres, vous pouvez préférer un pour l'expression.
Alexey Romanov le
13
Veuillez noter que l'exemple donné n'est pas équivalent à l'expression de la carte: c'est le même. Un pour la compréhension se traduit par des appels à map, flatMap et filter.
Daniel C.Sobral
9
La réponse commence comme ceci: "Il est utilisé dans les compréhensions de séquence (comme les compréhensions de liste et les générateurs de Python, où vous pouvez également utiliser le rendement)." Cela conduit à tort à penser que le rendement en Scala est similaire à celui en Python. Ce n'est pas le cas. En Python, le rendement est utilisé dans le contexte des coroutines (ou des continuations) alors que ce n'est pas le cas dans Scala. Pour plus de précisions, veuillez visiter ce fil: stackoverflow.com/questions/2201882/…
Richard Gomes
817

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

Tout d'abord, les forcompréhensions de Scala sont équivalentes à celles de Haskelldo notation , et ce n'est rien de plus qu'un sucre syntaxique pour la composition de multiples opérations monadiques. Comme cette déclaration n'aidera probablement personne qui a besoin d'aide, réessayons… :-)

Les forcompréhensions de Scala sont du sucre syntaxique pour la composition de plusieurs opérations avec la carte, flatMapet filter. Ou foreach. Scala traduit en fait une for-expression 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 des compréhensions.

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

  1. Ce

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

    est traduit en

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

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

    est traduit en

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

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

    est traduit sur Scala 2.7 en

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

    ou, sur Scala 2.8, en

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

    avec un repli sur l'ancienne méthode if withFilter n'est pas disponible mais l' filterest. Veuillez consulter la section ci-dessous pour plus d'informations à ce sujet.

  4. Ce

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

    est traduit en

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

Lorsque vous regardez des forcompréhensions très simples , le map/foreach alternatives semblent meilleures. Une fois que vous commencez à les composer, vous pouvez facilement vous perdre dans les parenthèses et les niveaux d'imbrication. Lorsque cela se produit, les forcompréhensions sont généralement beaucoup plus claires.

Je vais montrer un exemple simple et omettre intentionnellement toute explication. Vous pouvez décider quelle syntaxe était 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. La filterméthode a son comportement défini en fonction de la rigueur de la collection. Pour mieux comprendre cela, jetons un coup d'œil à certains 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 filterest immédiatement appliquée avec List, retournant une liste des chances - depuis foundest false. Ce n'est qu'ensuite que foreachs'exécute, mais, à ce moment, le changement foundn'a plus de sens, comme cela filtera déjà été exécuté.

Dans le cas de Stream, la condition n'est pas appliquée immédiatement. Au lieu de cela, comme chaque élément est demandé par foreach, filterteste la condition, ce qui permet foreachde l'influencer found. Juste pour être clair, voici le code équivalent de 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 a causé de nombreux problèmes, car les gens s'attendaient à ce ifqu'ils soient considérés à la demande, au lieu d'être appliqués à l'ensemble de la collection au préalable.

Introduction de Scala 2.8 withFilter, qui est toujours non stricte, quelle que soit la rigueur de la collection. L'exemple suivant montre Listavec 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

Cela produit le résultat que la plupart des gens attendent, sans changer le filtercomportement. En Rangepassant , a été changé de non strict à strict entre Scala 2.7 et Scala 2.8.

Daniel C. Sobral
la source
2
Il existe une nouvelle méthode avecFilter dans scala 2.8. pour (x <- c; si cond), le rendement {...} est traduit en c. withFilter (x => cond) .map (x => {...}) dans scala2.8.
Eastsun
2
@Eastsun C'est vrai, bien qu'il y ait également un repli automatique. withFilterest censé être non strict également, même pour les collections strictes, ce qui mérite quelques explications. Je vais considérer cela ...
Daniel C. Sobral
2
@Daniel: Il y a un grand traitement de ce sujet même dans "Programmation en Scala", par Odersky, et al. (Je suis sûr que vous le savez déjà). +1 pour l'avoir montré.
Ralph
Les 2 premiers points sont corrects avec: 1. for(x <- c; y <- x; z <-y) {...}est traduit en c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}est traduit enc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik
Est-ce for(x <- c; y = ...) yield {...}vraiment traduit en c.map(x => (x, ...)).map((x,y) => {...})? Je pense que c'est traduit c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})ou il me manque quelque chose?
prostynick
23

Oui, comme l'a dit Earwicker, c'est à peu près l'équivalent de LINQ selectet a très peu à voir avec Ruby et Python yield. Fondamentalement, où en C # vous écririez

from ... select ??? 

à Scala, vous avez à la place

for ... yield ???

Il est également important de comprendre que les for-compréhensions ne fonctionnent pas seulement avec des séquences, mais avec tout type qui définit certaines méthodes, tout comme LINQ:

  • Si votre type définit juste map, il autorise des forexpressions composées d'un seul générateur.
  • S'il définit flatMapaussi bien que map, il permetfor expressions composées de plusieurs générateurs.
  • S'il définit foreach, il autorise for-loops sans rendement (à la fois avec des générateurs simples et multiples).
  • S'il définit filter, il autorise les forexpressions -filter commençant par un if dans l' forexpression.
Alexey Romanov
la source
2
@Eldritch Conundrum - Ce qui est assez intéressant, c'est le même ordre dans lequel les spécifications SQL d'origine sont décrites. Quelque part en cours de route, le langage SQL a inversé l'ordre, mais il est tout à fait logique de décrire d'abord ce que vous tirez, puis ce que vous attendez d'en retirer.
Jordan Parmer
13

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

Il n'apparaît que 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 très différent des «continuations impératives» activées par yield dans d'autres langages, 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 connaissez C #, il est plus proche de l' select opérateur de LINQ qu'il ne l'est yield return).

Daniel Earwicker
la source
1
il doit être "var doubled = for (n <- original) yield n * 2".
Russel Yang
11

Tenez compte de la for-comprehension suivante

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

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

" Pour chaque entier i, s'il est supérieur à 3, alors céder (produire) iet l'ajouter à la liste A."

En termes de notation mathématique de constructeur d’ensemble , la compréhension ci-dessus est analogue à

set-notation

qui peut être lu comme

" Pour chaque entier je, s'il est supérieur à 3, alors c'est un membre de l'ensemble UNE."

ou bien comme

" UNEest l'ensemble de tous les entiers je, tels que chacun jeest supérieur à 3."

Mario Galic
la source
2

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 termine son exécution, elle renvoie la collection de toutes les valeurs générées. Le rendement peut être utilisé comme de simples opérateurs arithmétiques 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!!

Manasa Chada
la source
Lorsque vous répondez à une question aussi ancienne (il y a plus de 9 ans), il est utile de souligner en quoi votre réponse est différente de toutes les autres réponses déjà soumises.
jwvh
Je pensais que clarifier le doute est important et ne pas donner de réponse différente car même moi, je suis aussi un débutant qui apprend cette langue. Merci pour la suggestion.
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 )

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.

dotnetN00b
la source
-3

le rendement est plus flexible que map (), voir l'exemple ci-dessous

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 )

le rendement affichera le résultat comme: Liste (5, 6), ce qui est bon

tandis que map () retournera un résultat comme: List (false, false, true, true, true), ce qui n'est probablement pas ce que vous voulez.

Michael Peng
la source
4
Cette comparaison est fausse. Vous comparez deux choses différentes. L'expression dans yield ne fait en aucun cas la même chose que l'expression dans map. De plus, il ne montre pas du tout la "flexibilité" du rendement par rapport à la carte.
dotnetN00b