scalatutorial.de

Stufe 2Sprachkern · Lektion 10 von 34

for-Ausdrücke

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 5/7 dieser Stufe

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

 Scala 3
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:

 Scala 3
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:

 Scala 3
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:

 Scala 3
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:

 Scala 3
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:

 Scala 3
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-Ausdruckwird 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 xxs.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:

 Scala 3
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 None

Genau dieser Mechanismus steckt hinter dem Begriff Monade.

Passt dazu