2009-06-27 17 views

risposta

178

Viene utilizzato in sequence comprehensions (come le liste di liste e generatori di Python, dove è possibile utilizzare anche yield).

Viene applicato in combinazione con for e scrive un nuovo elemento nella sequenza risultante.

semplice esempio (da 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 corrispondente espressione in F # sarebbe

[ for a in args -> a.toUpperCase ] 

o

from a in args select a.toUpperCase 

in Linq.

Ruby's yield ha un effetto diverso.

+49

Quindi perché dovrei usare rendimento anziché mappa? Questo codice mappa è equivalente val res = args.map (_. ToUpperCase), giusto? – Geo

+4

Nel caso ti piaccia la sintassi migliore. Inoltre, come sottolinea alexey, le comprensioni forniscono anche una buona sintassi per l'accesso a flatMap, filter e foreach. –

+2

Preferisco di gran lunga la sintassi args map {_ toUpperCase} personalmente perché "sente" molto più OO. Sembra più in linea con l'obiettivo di Scala di fornire tramite supporto di libreria ciò che è disponibile solo in altre lingue tramite costrutti su misura e parole chiave –

10

A meno che non si ottenga una risposta migliore da un utente di Scala (che non sono), ecco la mia comprensione.

Appare solo come parte di un'espressione che inizia con for, che indica come generare un nuovo elenco da un elenco esistente.

Qualcosa di simile:

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

Quindi c'è un elemento di uscita per ogni ingresso (anche se credo che ci sia un modo di far cadere i duplicati).

Questo è abbastanza diverso dalle "continuative imperative" abilitate dal rendimento in altri linguaggi, dove fornisce un modo per generare un elenco di qualsiasi lunghezza, da un codice imperativo con quasi qualsiasi struttura.

(Se hai familiarità con C#, è più vicino all'operatore LINQ'sselect che a yield return).

+1

dovrebbe essere "var doubled = for (n <- original) yield n * 2 ". –

+0

Questo è quello che riesco a capire facilmente e chiaramente. –

21

Sì, come ha detto Earwicker, è praticamente equivalente a select di LINQ e ha ben poco a che fare con Ruby's e Python yield. In sostanza, dove in C# si può scrivere

from ... select ??? 

a Scala si dispone invece

for ... yield ??? 

E 'anche importante capire che for -comprehensions non lavorano solo con le sequenze, ma con qualsiasi tipo che definisce certi metodi, proprio come LINQ:

  • Se il tipo definisce solo map, permette for -expressions costituito da un 0.123.generatore singolo.
  • Se definisce flatMap e map, consente for -espressioni costituite da di diversi generatori.
  • Se definisce foreach, consente for -loops senza rendimento (sia con generatori singoli che multipli).
  • Se definisce filter, consente espressioni for -filter che iniziano con if nell'espressione for.
+1

nel LINQ di C#, l'ordine corretto è" da ... selezionare ??? " –

+2

@Eldritch Conundrum - Che interessante è lo stesso ordine in cui il In qualche modo lungo la strada il linguaggio SQL ha invertito l'ordine, ma ha perfettamente senso descrivere per prima cosa ciò che si sta estraendo, seguito da ciò che ci si aspetta di ottenere da esso. –

758

Penso che la risposta accettata sia ottima, ma sembra che molte persone non siano riuscite a cogliere alcuni punti fondamentali.

In primo luogo, le "per comprensioni" di Scala equivalgono alla notazione di "do" di Haskell e non è altro che uno zucchero sintattico per la composizione di più operazioni monadiche. Poiché questa affermazione probabilmente non aiuterà nessuno che ha bisogno di aiuto, proviamo di nuovo ... :-)

Scala "per comprensioni" è zucchero sintattico per la composizione di più operazioni con mappa, flatMap e filtro. O foreach. Scala in realtà traduce una for-expression in chiamate a quei metodi, quindi qualsiasi classe che fornisce loro, o un sottoinsieme di essi, può essere usata con per le comprensioni.

In primo luogo, parliamo delle traduzioni. Ci sono regole molto semplici:

1) Questo

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

si traduce in

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

2) Questo

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

si traduce in

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

3) Questa

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

si traduce in Scala 2.7 in

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

o, Scala 2,8, in

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

con un fallback nella ex se metodo withFilter è non disponibile ma è filter. Si prega di consultare la modifica qui sotto per ulteriori informazioni su questo.

4) Questo

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

si traduce in

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

Quando si guarda molto semplice per comprensioni, le alternative mappa/foreach guardare, anzi, meglio. Una volta che inizi a comporli, però, puoi facilmente perderti tra parentesi e livelli di nidificazione. Quando ciò accade, in genere le comprensioni sono molto più chiare.

Mostrerò un semplice esempio e intenzionalmente qualsiasi spiegazione. Puoi decidere quale sintassi è più facile da capire.

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 

EDIT

Scala 2.8 ha introdotto un metodo chiamato withFilter, la cui differenza principale è che, invece di restituire una nuova, filtrata, raccolta, filtra on- richiesta. Il metodo filter ha il suo comportamento definito in base alla severità della raccolta. Per capire meglio, diamo uno sguardo ad alcuni Scala 2.7 con List (rigoroso) e Stream (non rigida):

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 differenza si verifica perché il filtro è immediatamente applicato con List, restituendo un elenco di probabilità - - dal found è false. Solo a questo punto viene eseguito foreach, ma, a questo punto, la modifica di found non ha alcun significato, poiché filter è già stata eseguita.

Nel caso di Stream, la condizione non viene applicata immediatamente. Invece, poiché ogni elemento è richiesto da foreach, il test filter verifica la condizione, che consente a foreach di influenzarlo tramite found. Giusto per chiarire, ecco il codice equivalente per-comprensione:

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) 

Questo ha causato molti problemi, perché le persone si aspettavano che la if da considerarsi on-demand, invece di essere applicata a tutta la collezione in anticipo.

Scala 2.8 ha introdotto withFilter, che è sempre non rigida, indipendentemente dalla severità della raccolta. L'esempio seguente mostra List con entrambi i metodi su 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 

Questo produce il risultato più la gente si aspetta, senza cambiare how filter si comporta. Come nota a margine, Range è stato modificato da non rigido a stretto tra Scala 2.7 e Scala 2.8.

+2

C'è un nuovo metodo withFilter in scala 2.8. per (x <- c; se cond) yield {...} si traduce in c.withFilter (x => cond) .map (x => {...}) in scala2.8. – Eastsun

+1

@Eastsun Abbastanza vero, anche se esiste anche il fallback automatico. 'withFilter' dovrebbe essere anche non severo, anche per le collezioni rigorose, che merita qualche spiegazione. Prenderò in considerazione questo ... –

+35

Una delle migliori risposte che ho visto su SO. +1 – Allyn

-2

resa è più flessibile rispetto mappa(), vedi esempio sotto

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) 

rendimento si tradurrà come stampare: Lista (5, 6), che è buono

mentre map() restituirà risultati come: Elenco (falso, falso, vero, vero, vero), che probabilmente non è ciò che si intende.

+4

Questo confronto è sbagliato. Stai confrontando due cose diverse. L'espressione in resa non fa assolutamente la stessa cosa dell'espressione sulla mappa. Inoltre, non mostra affatto la "flessibilità" della resa rispetto alla mappa. – dotnetN00b

+0

yield al + 1> 3 farebbe lo stesso trucco – philix

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) 

Queste due parti di codice sono equivalenti.

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

println(res3) 
println(res4) 

Queste due parti di codice sono anche equivalenti.

La mappa è flessibile come la resa e viceversa.

4

Si consideri il seguente for-comprehension

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

Può essere utile per leggere ad alta voce la seguente

"Per ogni intero i, se è maggiore di 3, poi cedere (prodotto) i e aggiungerlo alla lista A. "

In termini di matematica set-builder notation, il sopra per-comprensione è analogo a

set-notation

che può essere letta come

"Per ogni intero i, se essa è maggiore di 3, quindi è membro del set A. "

o in alternativa come

"A è l'insieme di tutti gli interi i, in modo che ciascuna i è maggiore di 3".