Einstieg

In [SQ1] beschreiben Martin Odersky et al. den Scala for Ausdruck als Scala's Schweizer Armeemesser der Iteration. "Scala's for expression is a Swiss army knife of Iteration."

for Ausdruck? Warum nicht for Schleife? Hier sei angemerkt, dass in Scala ein sogenannter for Ausdruck keine Schleife im ursprünglichen Sinn darstellt. In Scala iterieren wir über eine Menge vorgegebener Daten. Diese Daten müssen nicht einmal einer Collection enstammen, sondern der Typ, über den wir iterieren, muss nur ein paar bestimmte höhere Funktionen implementieren, worauf wir aber erst später einghen wollen.

Im Bezug zu Scala's for finden wir in der englischsprachigen Literatur immer wieder die Bezeichnungen for - expression und for - comprehension, die darauf hindeuten, dass Scala's for Ausdruck keine eigentliche Schleife ist. Wenn es aber erlaubt ist das englische Wort loop mit Schleife zu übersetzen, so kann man es uns nicht übel nehmen, wenn wir im deutschsprachigen Raum von for Schleifen sprechen. Wir befinden uns damit auch in guter Gesellschaft, da unter anderen in [SQ1] (Odersky et. al. - Programming in Scala) mehrfach von for loops die Rede ist (z.B. S.48).

Gerade Umsteiger aus imperativen oder objektorientierten Sprachen, wie Java, können sich den for Ausdruck als for Schleife vorstellen. Die grundlegenden Funktionen wirken wie eine Schleife. Aber: Scala's for Ausdruck kann mehr als Int Werte zu durchlaufen.

Bereiche ganzzahliger Werte durchlaufen

Der for - Ausdruck wird oft dazu verwendet eine Int - Variable einen bestimmten Wertebereich durchlaufen zu lassen. Das nachfolgende Beispiel zeigt einen for - Ausdruck, wo eine Variable i die Werte 0 bis 4 annimmt und der aktuelle Wert ausgegeben wird.

for (i <- 0 to 4)
  println(i)
            

Der Ablauf dieses Ausdrucks führt zu folgender Ausgabe:

0
1
2
3
4
            

Möchte man, dass der letzte Wert nicht ausgegeben wird, kann man folgende Variante des for - Ausdrucks verwenden:

for (i <- 0 until 4)
  println(i)
            

Der Ablauf dieses Ausdrucks führt zu folgender Ausgabe:

0
1
2
3
            

Die oben vorgestellten for - Ausdrücke können den Eindruck hinterlassen, dass es sich bei der Variable i um eine var Variable handelt, da der Wert bei jedem Schleifendurchlauf verändert wird. Dies ist jedoch nicht der Fall. Bei jedem Schleifendurchlauf wird eine neue val Variable für i verwendet. Eine Zuweisung eines neuen Wertes für i ist innerhalb des des Auswertungsrumpfes nicht möglich.

Auch wenn der for Ausdruck nicht auf klassische Collections beschränkt ist, wollen wir zunächst der einfachheit halber, davon ausgehen.

<- im hier gezeigten for - Ausdruck wird als Generator (eng. generator) bezeichnet. <- generiert individuelle Werte aus einer Collection zur Verwendung in einem Ausdruck 1. Rechts von <- steht die Collection, aus der die Werte entnommen werden sollen. Links von <- steht der Name einer val Variablen, auf die wir innerhalb des Auswertungsrumpfes zugreifen können. Wenn also <- Elemente aus einer Collection generiert, müssen auch die Ausdrücke 0 to 4 und 0 until 4 als Ergebnis eine Collection liefern, was auch der Fall ist. Die beiden Ausdrücke liefern eine Collection vom Typ scala.collection.immutable.Range (bzw. scala.collection.immutable.Range.Inclusive bei to), welche Int- Elemente in einem Bereich zwischen einem Start- und Endwert bereitstellt. Der Umstand, dass eine Collection im for Ausdruck verwendet wird, führt dazu, dass wir die Collection auch außerhalb des for Ausdruckes definieren können und mithilfe einer Variablen dem for Ausdruck zuführen können.

scala> val myRange = 1 to 3
myRange: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

scala> for (i <- myRange) println(i)
1
2
3
itmapa.de - X2H V 0.11

In den bisherigen Beispielen haben wir Int Literale verwendet. Wenn wir uns die API Dokumentation ansehen, werden wir feststellen, dass für Int die Funktionen to und until nicht definiert sind. Damit die Funktionen zur Verfügung stehen, wird der Int Wert zunächst (durch impliziete Typumwandlung) in einen scala.runtime.RichInt Typ umgewandelt, für den die Funktionen definiert sind. Die impliziete Typkonvertierung ist in scala.LowPriorityImplicits definiert, welche durch scala.Prefed erweitert wird.

In den for - Ausdrücken, die wir bisher kennengelernt haben, muss nicht unbedingt eine Collection vom Typ Range verwendet werden. Wir können auch problemlos dem Generator eine Collection vom Typ List zur Verarbeitung übergeben.

scala> val myList = List(1,2,3)
myList: List[Int] = List(1, 2, 3)

scala> for (i <- myList) println(i)
1
2
3
itmapa.de - X2H V 0.11

Bereiche ganzzahliger Werte mit definierter Schrittweite

In den bisherigen for Ausdrücken sind wir Int - Werte immer mit einem Inkrement von 1 durchlaufen. Die beiden Methoden to und until sind in RichInt überladen, sodass wir das Inkrement festlegen können.


def to (end: Int, step: Int): Inclusive
def to (end: Int): Inclusive

def until (end: Int, step: Int): Range
def until (end: Int): Range
            

Das nachfolgende Beispiel zeigt einen for Ausdruck, welcher Int - Werte mit einem Inkrement von 3 durchläuft.

scala> for (i <- 4 to (17,3)) println(i)
4
7
10
13
16
itmapa.de - X2H V 0.11

Die hier gezeigte Vorgehensweise gilt Analog für die Methode until.

Die Int - Werte müssen nicht in aufsteigender Reihenfolge durchlaufen werden, vielmehr können wir auch ein Dekrement (die Werte werden kleiner) als Schrittweite festlegen.

scala> for (i <- 13 until (6,-2)) println(i)
13
11
9
7
itmapa.de - X2H V 0.11

Nicht Int-Collection

Bisher haben wir nur for-Ausdrücke mit Int-Werten verwendet. Wir können jedoch auch Collections anderer (aller) Typen verwenden. Beispielhaft folgt nachfolgend eine Anwendung der for Ausdrucks auf eine List mit Strings.

scala> val myList = List("Hello","world","Scala","rocks")
myList: List[java.lang.String] = List(Hello, world, Scala, rocks)

scala> for (s <- myList) println(s)
Hello
world
Scala
rocks
itmapa.de - X2H V 0.11

Mehrere Generatoren

In vielen Fällen möchten wir verschieden Werte verschachtelt durchlaufen. In Scala können wir die Verschachtelung in einer for Anweisung definieren. Beispielsweise können wir zwei Generatoren in einem for Ausdruck definieren, die dann verschachtelt durchlaufen werden.

scala> for (i <- 0 to 2; j <- 3 to 4) println(i+" "+j)
0 3
0 4
1 3
1 4
2 3
2 4
itmapa.de - X2H V 0.11

Verwenden wir statt der runden Klammern geschweifte Klammern und definieren die Generatoren in zwei Zeilen, können wir auf die Trennung mit einem Semikolon verzichten.

scala> for {i <- 0 to 2
     |      j <- 3 to 4} println(i+" "+j)
0 3
0 4
1 3
1 4
2 3
2 4
itmapa.de - X2H V 0.11

Filter

Nicht immer möchten wir alle Werte einer Collection in einem for Ausdruck verarbeiten. Zu diesem Zwecke stehen uns im for Ausdruck sogenannte Filter zur Verfügung. Ein Filter wird dabei einfach durch einen if-Ausdruck innerhalb des for Ausdrucks definiert. Liefert der Filter das Ergebnis true wird der aktuelle Schleifendurchlauf bearbeitet, liefert der Filter false wird der aktuelle Schleifendurchlauf nicht bearbeitet.

Im nachfolgenden Beispiel wenden wir einen for Ausdruck auf einen Range mit den Werten 1 bis 10 (inklusive) an und geben nur die geraden Werte auf der Systemausgabe aus.

object ForLoop {
  def main(args: Array[String]): Unit = {
    for {i <- 1 to 10
         if (i % 2 == 0)} println(i)
  }
}
itmapa.de - X2H V 0.17

Die Ausführung des Programms führt zu folgender Ausgabe auf der Systemausgabe:

2
4
6
8
10

itmapa.de - X2H V 0.17

Zugegeben, das gleiche hätten wir auch mit einer entsprechenden Range-Definition erreichen können, was aber nicht Inhalt des Abschnittes ist.

Wie bei den Generatoren sind wir bei Filter nicht auf einen einzigen if Ausdruck beschränkt und können mehrere Filter definieren. Im nachfolgenden Beispiel verwenden wir zwei Generatoren und zwei Filter. Zur Ausführung des Schleifeninhaltes kommt es im Beispiel nur, wenn die Variable i gerade und die Variable j ungerade ist.

object ForLoop2 {
  def main(args: Array[String]): Unit = {
    for {i <- 1 to 6
         if (i % 2 == 0)
         j <- 1 to 6
         if (j % 2 != 0)} println(i+" "+j)
  }
}
itmapa.de - X2H V 0.17

Die Ausführung des Programms führt zu folgender Ausgabe auf der Systemausgabe:

2 1
2 3
2 5
4 1
4 3
4 5
6 1
6 3
6 5
itmapa.de - X2H V 0.17

Die Filter haben keine bestimmte Position innerhalb des for-Ausdrucks. Greifen Filter aber auf Variable zu, die im for-Ausdruck definiert werden, müssen diese nach der Variablendefinition positioniert werden. Dementsprechend hätten wir unser obiges Beispiel auch folgendermaßen schreiben können.

object ForLoop2 {
  def main(args: Array[String]): Unit = {
    for {i <- 1 to 6         
         j <- 1 to 6
         if (i % 2 == 0)
         if (j % 2 != 0)} println(i+" "+j)
  }
}
itmapa.de - X2H V 0.17

Ergebnis eines for-Ausdrucks (yield)

Bis jetzt haben wir mit unseren for-Ausdrücken nur Seiteneffekte erzielt. In Scala wird jedoch propagiert, wenn möglich, die funktionale Programmierung zu bevorzugen. Seiteneffekte wiedersprechen der funktionalen Programmierung, sodass wir das Ergebnis der for-Ausdrücke auswerten müssen, um diese sinnvoll in der funktionalen Programmierung verwenden zu können.

Sehen wir uns zunächst das Ergebnis eines for-Ausdrucks, in der Form wie wir ihn bis jetzt verwendet haben, an.

object ForResult1 {

  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1,2,3,4)
    val result = for (i <- list) i * 2
    println(result)
  }
}
itmapa.de - X2H V 0.21

Als Ergebnis liefert uns dieses Programm (), also Unit. Mit diesem Ergebnis eines for-Ausdrucks können wir wenig anfangen. Abhilfe schafft hier das Schlüsselwort yield, welches dazu dient, die Ergebnisse der einzelnen Durchläufe "einzufangen".

object ForResult2 {

  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1,2,3,4)
    val result = for (i <- list) yield i * 2
    println(result)
  }
}
itmapa.de - X2H V 0.21

Als Ergebnis dieses for-Ausdrucks erhalten wir List(2, 4, 6, 8). Mithilfe von yield wird jedes einzelne Ergebnis des for-Ausdrucks in Form einer neuen Collection als Ergebnis des gesamten for-Ausdrucks zusammengefasst.


____________
1: Vergleiche [SQ2] S. 60