Mit Scala ist es recht einfach Funktionen zu schreiben, die sich in der Verwendung wie Sprachkonstrukte anfühlen. Das kann man sehr gut nutzen um textuelle DSLs zu entwickeln. Ein Beispiel ist das Testframework ScalaTest mit dem man Tests folgendermaßen formulieren kann:
map should (contain key ("one") and not contain value (10))
result should be => (0)
book should have (title ("Programming in Scala"))
tempFile should not be a ('directory)
quotient should be (1.0 plusOrMinus 0.2)
Als kleine Demonstration zeige ich hier nun wie man das ‘using’ Konstrukt aus C# in Scala nachahmen kann. Die Funktionsweise dieses Blocks ist simpel, aber immer wieder nützlich. Man übergibt einen Stream bzw. irgendeine Instanz, die IDisposable implementiert, verwendet diese und das Schließen bzw. Zerstören der Instanz geschieht ganz von alleine.
using (var file = File.CreateText("test.dat")
{
file.WriteLine("C# is");
file.WriteLine("great");
}
Was wirklich passiert ist, dass der Compiler ein try finally Block erzeugt, der in jedem Fall .Dispose() aufruft, womit dann im Falle eines Streams auch automatisch .Close() zum Zuge kommt. Fangen wir mit der Scala-Übersetzung an. Eine erste Methode könnte eine simple Funktion höherer Ordnung in der folgenden Art sein:
val file = new PrintWriter("test.dat")
using (file, () => {
file.println("scala is")
file.append("great")
})
def using(stream: Closeable, operation: () => Unit) {
try {
operation
} finally {
stream.close()
}
}
Using erwartet den Stream sowie die auszuführende Funktion, die nichts erwartet und nichts zurückgibt (Unit entspricht praktisch void in C#). Wir können die Syntax noch weiter verbessern, indem wir den zweiten Parameter in einen By-Name-Parameter umschreiben:
def using(stream: Closeable, operation => Unit)
Nun fehlen die zwei Klammern in der Signatur, was dazu führt, dass wir sie auch beim Aufrufen weglassen können:
using (file, { ... })
Das kommt der Version aus C# schon recht nahe. Einen Unterschied können wir jedoch nicht verstecken – in Scala sind Variablendeklarationen keine Ausdrücke. Somit können wir sie z.B. nicht wie in C an eine Methode übergeben, sondern müssen die Variable im Voraus definieren und sie dann an using übergeben.
Doch trotzdem gibt es hier noch Verbesserungsmöglichkeiten. Wir können using in eine partiell auswertbare Methode umwandeln, sodass using einen Parameter erwartet und als Ergebnis wieder eine Funktion zurück liefert, die nur noch die Operation als Argument bekommt. Das sieht dann so aus:
def using(stream: Closeable)(operation: => Unit) { ... }
Damit sind wir nunam Ziel angelangt. Wir können das using-Konstrukt fast wie in C# nutzen (in Scala können normale Klammern auch durch geschweifte ersetzt werden):
val file = new PrintWriter("test.dat")
using (file) {
file.println("scala is")
file.append("great")
}
Heiko 12:37 on 22.02.2010 Permalink
Schöne Demo!
Eine Anmerkung: Wir müssen und sollten die Variable file gar nicht im Voraus definieren, wenn wir sie nur innerhalb von using verwenden wollen. Besser wäre
using (new PrintWriter(“test.dat”)) {
…
}
Michael 06:20 on 23.02.2010 Permalink
@Heiko: Aber wie soll man dann Methoden des Streams aufrufen können?