Was ist Scalas Ertrag?

Antworten:

205

Es wird in Sequenzverständnissen verwendet (wie Pythons Listenverständnis und Generatoren, wo Sie es auch verwenden können yield).

Es wird in Kombination mit angewendet forund schreibt ein neues Element in die resultierende Sequenz.

Einfaches Beispiel (von 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)
  }
}

Der entsprechende Ausdruck in F # wäre

[ for a in args -> a.toUpperCase ]

oder

from a in args select a.toUpperCase 

in Linq.

Ruby's yieldhat einen anderen Effekt.

Dario
quelle
57
Warum sollte ich Yield anstelle von Map verwenden? Dieser Kartencode ist äquivalent val res = args.map (_. ToUpperCase), richtig?
Geo
4
Falls Ihnen die Syntax besser gefällt. Wie Alexey betont, bieten Verständnisse auch eine gute Syntax für den Zugriff auf flatMap, Filter und foreach.
Nathan Shively-Sanders
22
Recht. Wenn Sie nur eine einfache Karte haben - einen Generator ohne Wenn -, würde ich sicher sagen, dass das Aufrufen der Karte besser lesbar ist. Wenn Sie mehrere voneinander abhängige Generatoren und / oder Filter haben, bevorzugen Sie möglicherweise einen for-Ausdruck.
Alexey Romanov
13
Bitte beachten Sie, dass das angegebene Beispiel nicht dem Kartenausdruck entspricht: Es ist dasselbe. A zum Verständnis wird in Aufrufe von Map, FlatMap und Filter übersetzt.
Daniel C. Sobral
9
Die Antwort beginnt folgendermaßen: "Sie wird in Sequenzverständnissen verwendet (wie Pythons Listenverständnissen und Generatoren, in denen Sie auch Yield verwenden können)." Dies führt fälschlicherweise zu der Annahme, dass der Ertrag in Scala dem Ertrag in Python ähnlich ist. Das ist nicht der Fall. In Python wird Yield im Kontext von Coroutinen (oder Fortsetzungen) verwendet, während dies in Scala nicht der Fall ist. Für weitere Informationen
Richard Gomes
816

Ich denke, die akzeptierte Antwort ist großartig, aber es scheint, dass viele Menschen einige grundlegende Punkte nicht verstanden haben.

Erstens entspricht Scalas forVerständnis dem von Haskelldo Notation , und es ist nichts weiter als ein syntaktischer Zucker für die Zusammensetzung mehrerer monadischer Operationen. Da diese Aussage höchstwahrscheinlich niemandem helfen wird, der Hilfe benötigt, versuchen wir es noch einmal… :-)

Scalas forVerständnis ist syntaktischer Zucker für die Komposition mehrerer Operationen mit Karte flatMapund filter. Oder foreach. Scala übersetzt einen forAusdruck tatsächlich in Aufrufe dieser Methoden, sodass jede Klasse, die sie bereitstellt, oder eine Teilmenge davon zum Verständnis verwendet werden kann.

Lassen Sie uns zunächst über die Übersetzungen sprechen. Es gibt sehr einfache Regeln:

  1. Diese

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

    wird übersetzt in

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

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

    wird übersetzt in

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

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

    wird auf Scala 2.7 in übersetzt

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

    oder auf Scala 2.8 in

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

    mit einem Fallback in die erstere, wenn Methode withFilternicht verfügbar ist, aber filterist. Weitere Informationen hierzu finden Sie im folgenden Abschnitt.

  4. Diese

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

    wird übersetzt in

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

Wenn Sie sich sehr einfache forErkenntnisse ansehen, sehen die map/ foreachAlternativen tatsächlich besser aus. Sobald Sie mit dem Komponieren beginnen, können Sie sich jedoch leicht in Klammern und Verschachtelungsebenen verlieren. Wenn dies geschieht, ist das forVerständnis normalerweise viel klarer.

Ich werde ein einfaches Beispiel zeigen und absichtlich jede Erklärung weglassen. Sie können entscheiden, welche Syntax leichter zu verstehen war.

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

oder

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

withFilter

In Scala 2.8 wurde eine Methode namens aufgerufen withFilter, deren Hauptunterschied darin besteht, dass anstelle einer neuen, gefilterten Sammlung bei Bedarf gefiltert wird. Das filterVerhalten der Methode wird basierend auf der Strenge der Sammlung definiert. Um dies besser zu verstehen, werfen wir einen Blick auf einige Scala 2.7 mit List(streng) und Stream(nicht streng):

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

Der Unterschied tritt auf, weil filtersofort angewendet Listwird und eine Liste der Gewinnchancen zurückgegeben wird - da foundist false. Erst dann foreachwird ausgeführt, aber zu diesem Zeitpunkt ist eine Änderung foundbedeutungslos, wie filterbereits ausgeführt.

Im Falle von Streamwird die Bedingung nicht sofort angewendet. Testen Sie stattdessen, wie jedes Element von angefordert wird foreach, filterdie Bedingung, foreachum sie zu beeinflussen found. Um es klar zu machen, hier ist der äquivalente Code für das Verständnis:

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)

Dies verursachte viele Probleme, da die Leute erwarteten if, dass das On-Demand-Verfahren berücksichtigt wird, anstatt es zuvor auf die gesamte Sammlung anzuwenden.

Es wurde Scala 2.8 eingeführt withFilter, die unabhängig von der Strenge der Sammlung immer nicht streng ist. Das folgende Beispiel zeigt Listmit beiden Methoden in 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

Dies führt zu dem Ergebnis, das die meisten Menschen erwarten, ohne das Verhalten zu ändern filter. Als Randnotiz Rangewurde zwischen Scala 2.7 und Scala 2.8 von nicht streng zu streng geändert.

Daniel C. Sobral
quelle
2
In Scala 2.8 gibt es eine neue Methode mit Filter. für (x <- c; wenn cond) Ausbeute {...} wird in scala2.8 in c.withFilter (x => cond) .map (x => {...}) übersetzt.
Eastsun
2
@ Eastsun Richtig, obwohl es auch einen automatischen Fallback gibt. withFiltersoll auch für strenge Sammlungen nicht streng sein, was eine Erklärung verdient. Ich werde das in Betracht ziehen ...
Daniel C. Sobral
2
@ Daniel: Es gibt eine großartige Behandlung dieses Themas in "Programming in Scala" von Odersky et al. (Ich bin sicher, dass Sie das schon wissen). +1 für die Anzeige.
Ralph
Die ersten 2 Punkte sind richtig mit: 1. for(x <- c; y <- x; z <-y) {...}wird übersetzt in c.foreach(x => x.foreach(y => y.foreach(z => {...}))) 2. for(x <- c; y <- x; z <- y) yield {...}wird übersetzt inc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik
Ist das for(x <- c; y = ...) yield {...}wirklich übersetzt c.map(x => (x, ...)).map((x,y) => {...})? Ich denke, es ist übersetzt c.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})oder ich vermisse etwas?
Prostynick
23

Ja, wie Earwicker sagte, es ist so ziemlich das Äquivalent zu LINQs selectund hat sehr wenig mit Rubys und Pythons zu tun yield. Grundsätzlich, wo in C # würden Sie schreiben

from ... select ??? 

in Scala haben Sie stattdessen

for ... yield ???

Es ist auch wichtig zu verstehen, dass for-Verständnis nicht nur mit Sequenzen funktioniert, sondern mit jedem Typ, der bestimmte Methoden definiert, genau wie LINQ:

  • Wenn Ihr Typ nur definiert map, erlaubt er forAusdrücke, die aus einem einzelnen Generator bestehen.
  • Wenn es flatMapso gut wie definiert map, erlaubt es forAusdrücke, die aus mehreren Generatoren bestehen.
  • Wenn es definiert ist foreach, erlaubt es for-schleifen ohne Ausbeute (sowohl mit einem als auch mit mehreren Generatoren).
  • Wenn es definiert ist filter, werden for-filter-Ausdrücke zugelassen, die mit einem if im forAusdruck beginnen.
Alexey Romanov
quelle
2
@Eldritch Conundrum - Interessanterweise ist dies dieselbe Reihenfolge, in der die ursprüngliche SQL-Spezifikation beschrieben wird. Irgendwann auf dem Weg hat die SQL-Sprache die Reihenfolge umgekehrt, aber es ist absolut sinnvoll, zuerst zu beschreiben, woraus Sie ziehen, gefolgt von dem, was Sie davon erwarten.
Jordan Parmer
13

Sofern Sie keine bessere Antwort von einem Scala-Benutzer erhalten (was ich nicht bin), ist hier mein Verständnis.

Es wird nur als Teil eines Ausdrucks angezeigt for, der mit beginnt und angibt, wie eine neue Liste aus einer vorhandenen Liste generiert wird.

Etwas wie:

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

Es gibt also ein Ausgabeelement für jede Eingabe (obwohl ich glaube, dass es eine Möglichkeit gibt, Duplikate zu löschen).

Dies unterscheidet sich erheblich von den "imperativen Fortsetzungen", die durch die Ausbeute in anderen Sprachen ermöglicht werden. Dort können Sie eine Liste beliebiger Länge aus einem imperativen Code mit nahezu beliebiger Struktur erstellen.

(Wenn Sie mit C # vertraut sind, ist es dem select Operator von LINQ näher als dem yield return).

Daniel Earwicker
quelle
1
es sollte "var double = = (n <- original) Ausbeute n * 2" sein.
Russel Yang
11

Beachten Sie zum Verständnis Folgendes

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

Es kann hilfreich sein, es wie folgt laut vorzulesen

Für jede ganze Zahl i, wenn sie größer als 3, dann ergibt (produzieren) iund füge sie die Liste A.“

In Bezug auf die mathematische Set-Builder-Notation ist das obige Verständnis analog zu

Set-Notation

was gelesen werden kann als

Für jede ganze Zahl ich, wenn sie größer als 3, dann ist ein Mitglied des Satzes EIN.“

oder alternativ als

" EINist die Menge aller ganzen Zahlen ich, so dass jede ichgrößer als ist 3."

Mario Galic
quelle
2

Die Ausbeute ähnelt der for-Schleife mit einem Puffer, den wir nicht sehen können, und fügt für jedes Inkrement das nächste Element zum Puffer hinzu. Wenn die for-Schleife beendet ist, wird die Sammlung aller erhaltenen Werte zurückgegeben. Die Ausbeute kann als einfache arithmetische Operatoren oder sogar in Kombination mit Arrays verwendet werden. Hier sind zwei einfache Beispiele zum besseren Verständnis

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

res: scala.collection.immutable.IndexedSeq [Int] = Vektor (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))

Hoffe das hilft!!

Manasa Chada
quelle
Bei der Beantwortung einer so alten Frage (vor über 9 Jahren) ist es hilfreich, darauf hinzuweisen, dass sich Ihre Antwort von allen anderen bereits eingereichten Antworten unterscheidet.
JWVH
Ich fand es wichtig, den Zweifel zu klären und nicht die andere Antwort zu geben, da selbst ich ein Anfänger bin, der diese Sprache lernt. Danke für den Vorschlag.
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 )

Diese beiden Codeteile sind äquivalent.

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

println( res3 ) 
println( res4 )

Diese beiden Codeteile sind ebenfalls gleichwertig.

Die Karte ist so flexibel wie der Ertrag und umgekehrt.

dotnetN00b
quelle
-3

Der Ertrag ist flexibler als map (), siehe Beispiel unten

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 )

Ausbeute druckt Ergebnis wie folgt: Liste (5, 6), was gut ist

while map () gibt das folgende Ergebnis zurück: List (false, false, true, true, true), was wahrscheinlich nicht das ist, was Sie beabsichtigen.

Michael Peng
quelle
4
Dieser Vergleich ist falsch. Sie vergleichen zwei verschiedene Dinge. Der Ausdruck in Ausbeute macht in keiner Weise dasselbe wie der Ausdruck in Karte. Außerdem zeigt es überhaupt nicht die "Flexibilität" des Ertrags im Vergleich zur Karte.
dotnetN00b