Qual è la resa di Scala?


Risposte:


205

È usato nelle comprensioni di sequenze (come la lista-comprensioni e generatori di Python, dove puoi yieldanche usare ).

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

Esempio semplice (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)
  }
}

L'espressione corrispondente in F # sarebbe

[ for a in args -> a.toUpperCase ]

o

from a in args select a.toUpperCase 

a Linq.

Ruby yieldha un effetto diverso.


57
Quindi perché dovrei usare la resa invece della mappa? Questo codice mappa è equivalente val res = args.map (_. ToUpperCase), giusto?
Geo,

4
Nel caso in cui ti piaccia meglio la sintassi. Inoltre, come sottolinea alexey, le comprensioni forniscono anche una buona sintassi per accedere a flatMap, filtro e foreach.
Nathan Shively-Sanders,

22
Destra. Se hai solo una semplice mappa - un generatore senza se - direi che chiamare la mappa è più leggibile. Se si dispone di più generatori che dipendono l'uno dall'altro e / o filtri, è possibile preferire un'espressione for.
Alexey Romanov,

13
Si noti che l'esempio fornito non è equivalente all'espressione della mappa: è la stessa. Un per comprensione è tradotto in chiamate per mappare, flatMap e filtro.
Daniel C. Sobral,

9
La risposta inizia in questo modo: "È usato nelle comprensioni di sequenze (come le comprensioni e i generatori di elenchi di Python, dove è possibile usare anche la resa)". Questo porta erroneamente a pensare che la resa in Scala sia simile alla resa in Python. Questo non è il caso. In Python, la resa è usata nel contesto di coroutine (o continuazioni) mentre non è il caso di Scala. Per ulteriori chiarimenti, visitare questa discussione: stackoverflow.com/questions/2201882/…
Richard Gomes,

817

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

In primo luogo, le forcomprensioni di Scala sono equivalenti alla donotazione di Haskell , e non è altro che uno zucchero sintattico per la composizione di molteplici operazioni monadiche. Poiché questa affermazione molto probabilmente non aiuterà nessuno che ha bisogno di aiuto, riproviamo ... :-)

La forcomprensione di Scala è lo zucchero sintattico per la composizione di più operazioni con la mappa flatMape filter. Or foreach. Scala in realtà traduce forun'espressione in chiamate a quei metodi, quindi qualsiasi classe che li fornisce, o un sottoinsieme di essi, può essere usata per comprendere.

Innanzitutto, parliamo delle traduzioni. Ci sono regole molto semplici:

  1. Questo

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

    è tradotto in

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

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

    è tradotto in

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

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

    è tradotto in Scala 2.7 in

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

    o, su Scala 2.8, in

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

    con un fallback nel primo se il metodo withFilternon è disponibile ma lo filterè. Per ulteriori informazioni, consultare la sezione seguente.

  4. Questo

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

    è tradotto in

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

Quando guardi a forcomprensioni molto semplici , le alternative map/ foreachsembrano davvero migliori. Una volta che inizi a comporli, tuttavia, puoi facilmente perderti tra parentesi e livelli di annidamento. Quando ciò accade, le forcomprensioni sono generalmente molto più chiare.

Mostrerò un semplice esempio e ometterò intenzionalmente qualsiasi spiegazione. Puoi decidere quale sintassi era 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

withFilter

Scala 2.8 ha introdotto un metodo chiamato withFilter, la cui principale differenza è che, invece di restituire una nuova raccolta filtrata, filtra su richiesta. Il filtermetodo ha il suo comportamento definito in base alla rigidità della raccolta. Per capirlo meglio, diamo un'occhiata ad alcuni Scala 2.7 con List(rigoroso) e Stream(non rigoroso):

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é filterviene immediatamente applicato con List, restituendo un elenco di probabilità - poiché lo foundè false. Solo allora foreachviene eseguito, ma, a questo punto, il cambiamento non ha foundsenso, come filterè già stato eseguito.

Nel caso di Stream, la condizione non viene applicata immediatamente. Invece, come richiesto da ogni elemento foreach, filterverifica la condizione, che consente foreachdi influenzarla found. Giusto per chiarire, ecco l'equivalente codice di 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)

Ciò ha causato molti problemi, perché le persone si aspettavano ifche venissero considerate su richiesta, anziché essere applicate in anticipo all'intera collezione.

Introduzione di Scala 2.8 withFilter, che è sempre non rigorosa, indipendentemente dalla severità della collezione. L'esempio seguente mostra Listcon 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 che molte persone si aspettano, senza cambiare il filtercomportamento. Come nota a margine, è Rangestato cambiato da non severo a rigoroso tra Scala 2.7 e Scala 2.8.


2
Esiste un nuovo metodo con Filtro in scala 2.8. per (x <- c; if cond) yield {...} è tradotto in c.withFilter (x => cond) .map (x => {...}) in scala2.8.
Eastsun,

2
@Eastsun Abbastanza vero, anche se c'è anche un fallback automatico. withFilterdovrebbe anche essere non rigoroso, anche per le raccolte rigorose, che merita qualche spiegazione. Lo prenderò in considerazione ...
Daniel C. Sobral,

2
@Daniel: C'è un grande trattamento di questo argomento in "Programmazione alla Scala", di Odersky, et al. (Sono sicuro che lo sai già). +1 per mostrarlo.
Ralph,

I primi 2 punti sono corretti con: 1. for(x <- c; y <- x; z <-y) {...}è tradotto in c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}è tradotto inc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik

Questo è for(x <- c; y = ...) yield {...}davvero tradotto in c.map(x => (x, ...)).map((x,y) => {...})? Penso che sia tradotto in c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})o mi manchi qualcosa?
prostynick,

23

Sì, come diceva Earwicker, è praticamente l'equivalente di LINQ selecte ha ben poco a che fare con Ruby e Python yield. Fondamentalmente, dove scrivere in C #

from ... select ??? 

in Scala invece hai

for ... yield ???

È anche importante capire che le comprensioni fornon funzionano solo con le sequenze, ma con qualsiasi tipo che definisce determinati metodi, proprio come LINQ:

  • Se il tuo tipo definisce solo map, consente for-espressioni costituite da un singolo generatore.
  • Se definisce flatMapoltre map, consente for-espressioni costituite da più generatori.
  • Se lo definisce foreach, consente for-loops senza rendimento (sia con generatori singoli che multipli).
  • Se definisce filter, permette forespressioni -Filter iniziano con l'espressione.iffor

2
Enigma di @Eldritch - Il che è abbastanza interessante nello stesso ordine in cui viene delineata la specifica SQL originale. Da qualche parte lungo la strada il linguaggio SQL ha invertito l'ordine, ma ha perfettamente senso prima descrivere da cosa stai attingendo seguito da cosa ti aspetti di uscirne.
Jordan Parmer,

13

A meno che tu non ottenga una risposta migliore da un utente di Scala (cosa che io 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 a:

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

Quindi c'è un elemento di output per ogni input (anche se credo che ci sia un modo per eliminare i duplicati).

Questo è abbastanza diverso dalle "continuazioni imperative" abilitate dalla resa in altre lingue, 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 di LINQ select che a yield return).


1
dovrebbe essere "var raddoppiato = per (n <- originale) resa n * 2".
Russel Yang,


11

Considera la seguente comprensione

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

Potrebbe essere utile leggerlo ad alta voce come segue

" Per ogni numero intero i, se è maggiore di 3, allora produce (produce) ie aggiungilo alla lista A."

In termini di notazione matematica del set-builder , la comprensione di cui sopra è analoga a

set-notazione

che può essere letto come

" Per ogni numero intero io, se è maggiore di 3, allora è un membro dell'insieme UN."

o in alternativa come

" UNè l'insieme di tutti i numeri interi io, tale che ciascuno ioè maggiore di 3."


2

Il rendimento è simile al ciclo for che ha un buffer che non possiamo vedere e per ogni incremento continua ad aggiungere l'elemento successivo al buffer. Quando il ciclo for termina l'esecuzione, restituisce la raccolta di tutti i valori prodotti. La resa può essere utilizzata come semplici operatori aritmetici o anche in combinazione con array. Ecco due semplici esempi per una migliore comprensione

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)] = List ((1, a), (1, b), (1, c), (2, a), (2, b), (2, c), ( 3, a), (3, b), (3, c))

Spero che questo ti aiuti!!


Quando rispondi a una domanda così vecchia (oltre 9 anni fa) è utile sottolineare come la tua risposta è diversa da tutte le altre risposte già inviate.
jwvh,

Ho pensato che chiarire il dubbio fosse importante e non dare una risposta diversa poiché anche io sono anche un principiante che sta imparando questa lingua. Grazie per il suggerimento
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 )

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 )

Anche queste due parti di codice sono equivalenti.

La mappa è flessibile quanto la resa e viceversa.


-3

la resa è più flessibile di map (), 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 )

la resa stamperà il risultato come: Elenco (5, 6), il che è buono

mentre map () restituirà risultati come: List (false, false, true, true, true), che probabilmente non è quello che intendi.


4
Quel confronto è sbagliato. Stai confrontando due cose diverse. L'espressione in resa non fa in alcun modo la stessa cosa dell'espressione nella mappa. Inoltre, non mostra affatto la "flessibilità" della resa rispetto alla mappa.
dotnetN00b
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.