Je comprends Ruby et le rendement de Python. Quel est le rendement de Scala?
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.
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:
Cette
for(x <- c1; y <- c2; z <-c3) {...}
est traduit en
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
Cette
for(x <- c1; y <- c2; z <- c3) yield {...}
est traduit en
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
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.
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.
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:
map
, il autorise for
- expressions consistant en un seul générateur.flatMap
ainsi que map
, elle autorise for
- expressions composées de plusieurs générateurs.foreach
, il autorise for
- boucles sans rendement (avec un seul et plusieurs générateurs).filter
, il autorise les expressions for
- commençant par if
dans l'expression for
.À 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
).
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?
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 à
qui peut être lu comme
" Pour chaque entier , si il est supérieur à , alors il est un membre de l'ensemble . "
ou alternativement comme
" est l'ensemble de tous les entiers , de sorte que chaque est supérieur à . "
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!!
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.