Stufe 2Sprachkern · Lektion 10 von 34
for-Ausdrücke
Das for in Scala sieht aus wie eine Schleife, ist aber ein Ausdruck, der neue Sammlungen erzeugen kann. Hinter der Kulisse steht ein einfacher Mechanismus, der später auch Option, Try und Future zugutekommt.
Die Grundform
for zahl <- List(1, 2, 3) do
println(zahl)Der Teil zahl <- List(1, 2, 3) heißt Generator: Er nimmt nacheinander jedes Element der Liste und bindet es an den Namen zahl. Mit do wird der Rumpf für jedes Element ausgeführt. Das Ergebnis ist Unit – ein solcher for-Ausdruck dient nur der Wirkung, zum Beispiel der Ausgabe.
Neue Sammlungen mit yield
Ersetzen Sie do durch yield, entsteht eine neue Sammlung:
val preise = List(10.0, 25.0, 4.5)
val mitSteuer = for p <- preise yield p * 1.19
// List(11.9, 29.75, 5.355)Der Typ der Ergebnis-Sammlung richtet sich nach dem Typ des ersten Generators: Aus einer List wird eine List, aus einem Vector ein Vector, aus einem Bereich 1 to 5 ein IndexedSeq.
Filter
Mit if lassen sich Elemente überspringen:
val gerade = for n <- 1 to 20 if n % 2 == 0 yield n
// Vector(2, 4, 6, …, 20)Mehrere Generatoren
Mehrere Generatoren werden verschachtelt durchlaufen – der letzte am schnellsten, wie bei verschachtelten Schleifen:
val paare =
for
x <- 1 to 3
y <- 'a' to 'b'
yield s"$x$y"
// Vector(1a, 1b, 2a, 2b, 3a, 3b)Bei mehreren Zeilen schreiben Sie for allein in eine Zeile und rücken die Generatoren ein. In Scala 2 standen sie in geschweiften Klammern: for { x <- … ; y <- … } yield ….
Zwischenwerte
Innerhalb eines for-Ausdrucks können Sie Zwischenergebnisse benennen:
val dreiecke =
for
a <- 1 to 20
b <- a to 20
c2 = a * a + b * b
c = math.sqrt(c2).toInt
if c * c == c2 && c <= 20
yield (a, b, c)
// Vector((3,4,5), (5,12,13), (6,8,10), (8,15,17), (9,12,15), (12,16,20))Das Beispiel sucht alle pythagoreischen Tripel bis 20. c2 = … ist kein Generator, sondern eine einfache Definition, die in jedem Durchlauf neu berechnet wird.
Muster im Generator
Auf der linken Seite eines Generators darf ein Muster stehen. Das ist praktisch bei Tupeln und Maps:
val hauptstaedte = Map("Deutschland" -> "Berlin", "Frankreich" -> "Paris")
for (land, stadt) <- hauptstaedte do
println(s"$stadt liegt in $land")Was der Compiler daraus macht
for-Ausdrücke sind syntaktischer Zucker. Der Compiler übersetzt sie in Aufrufe von foreach, map, flatMap und withFilter:
| for-Ausdruck | wird zu |
|---|---|
for x <- xs do f(x) | xs.foreach(x => f(x)) |
for x <- xs yield f(x) | xs.map(x => f(x)) |
for x <- xs if p(x) yield x | xs.withFilter(x => p(x)).map(x => x) |
for x <- xs; y <- ys yield (x, y) | xs.flatMap(x => ys.map(y => (x, y))) |
Das hat eine wichtige Folge: for funktioniert mit jedem Typ, der diese Methoden anbietet – nicht nur mit Listen. Mit Option, Try, Either und Futures lassen sich so mehrere Schritte verketten, von denen jeder fehlschlagen oder später fertig werden kann:
def alterAusText(s: String): Option[Int] = s.toIntOption.filter(_ >= 0)
val summe =
for
a <- alterAusText("34")
b <- alterAusText("41")
yield a + b
// Some(75); wäre eine Eingabe ungültig, ergäbe sich NoneGenau dieser Mechanismus steckt hinter dem Begriff Monade.