Scala enthält nur wenige Kontrollstrukturen. Aber wir können in Scala mit den Mitteln der Sprache selbst “eigene” Kontrollstrukturen implementieren, die so ausschauen, als seien sie in der Sprache selbst definiert. Wir können also die Sprache “erweitern”.
Im Folgenden werden wir eine repeat-until-Schleife implementieren. Diese wird in einigen aufeinander folgenden Schritten gezeigt. Die letzte Gestalt der Schleife wird sich wie folgt nutzen lassen:
import util.ControlC.repeat var i = 0 repeat { print(i + " ") i += 1 } until i == 4
Die Ausgaben dieser Schleife sind nicht überraschend:
0 1 2 3
Hier die erste Version der Schleife:
object ControlA { class Until(block: Function0[Unit]) { def until(condition: Function0[Boolean]) = { block.apply() while (!condition.apply()) block.apply() } } def repeat(block: Function0[Unit]): Until = { new Until(block) } }
An repeat wird ein block übergeben (eine Referenz des Typs Function0[Unit]. Die Methode erzeugt ein Until-Objekt, dessen Konstruktor sie den block weiterreicht und liefert dieses Until-Objekt zurück. Auf das Until-Objekt kann dann die Methode until aufgerufen werden, welcher eine condition des Typs Function0[Boolean] übergeben wird. Die until-Methode ruft dann zunächst einmal die apply-Methode auf den übergebenen block auf. Dann wird die apply-Methode der condition aufgerufen. Solange diese jeweils true liefert, wird der block erneut ausgeführt.
Eine mögliche Anwendung:
import util.ControlA.repeat var i = 0 repeat(new Function0[Unit] { override def apply() = { print(i + " ") i += 1 } }). until(new Function0[Boolean] { override def apply(): Boolean = { i == 4 } })
Die explizite Verwendung des Function0-Interfaces bedeutet zwar viel Schreibaufwand – aber hier wird zunächst einmal deutlich, was tatsächlich (auch in den späteren Varianten) passiert. Wir definieren und instanziieren zwei anonyme Klassen: die apply-Methode der ersten Klasse enthält den auszuführenden block, die apply-Methode der zweiten Klasse repräsentiert die condition.
Funktionale Schreibweise
Wir können die obige “objekt-orientierte” Schreibeweise natürlich auch durch die “funktionale” Schreibweise ersetzen.
Hier zunächst die zweite repeat-until-Variante:
object ControlB { class Until(block: () => Unit) { def until(condition: () => Boolean) = { block.apply() while (!condition.apply()) block.apply() } } def repeat(block: () => Unit): Until = { new Until(block) } }
Wobei wir statt block.apply() natürlich auch einfach block() schreiben könnten – und statt condition.apply() einfach condition().
Hier eine Anwendung (die allerdings auch mit ControlA funktionieren würde):
import util.ControlB.repeat var i = 0 repeat(() => { print(i + " ") i += 1 }). until(() => i == 4 )
Das sieht schon schöner aus…
Wir definieren eine dritte Variante der Schleife:
object ControlC { class Until(block: => Unit) { def until(condition: => Boolean) = { block while (!condition) block } } def repeat(block: => Unit): Until = { new Until(block) } }
Die Parameter sowohl von repeat, vom Until-Konstruktor als auch von der until-Methode sind nun als Parameter definiert, die lazy ausgewertet werden. Man beachte: Hier wird z.B. block nicht mehr als ()=>Unit, sondern einfache als =>Unit definiert!
Eine mögliche Anwendung dieser neuen Varianten:
import util.ControlC.repeat var i = 0 repeat({ print(i + " ") i += 1 }). until( i == 4 )
Wir können nun die runden Klammern durch geschweifte ersetzten (sofern eine Methode nur einen einzigen Parameter hat); und wir können until in der Infix-Schreibweise (als Infix-Operator) notieren – dann entfällt der Punkt vor until und die Klammern des until-Parameters:
import util.ControlC.repeat var i = 0 repeat({ print(i + " ") i += 1 }). until( i == 4 )
Damit ist unser Ziel erreicht: wir haben eine repeat-until-Schleife gebaut, die so ausschaut, als sei sie Bestandteil der Sprache.
Allerdings können wir die Implementierung noch einmal verkürzen:
object ControlD { def repeat(block: => Unit) = { new AnyRef { def until(condition: => Boolean) = { block while (!condition) block } } } }
Wir ersparen uns die Definition einer expliziten Until-Klasse und erzeugen einfach ein Objekt einer von AnyRef abgeleiteten anonymen Klasse.
Integrata Seminare zu Scala: