Mittwoch, 26. August 2009

Closures in JavaFX Script

JavaFX Script (die Programmiersprache für Suns neue RIA-Technologie JavaFX) ist eine objektorientierte, deklarative und funktionale Programmiersprache.

Als funktionale Sprache unterstützt sie Funktionen höherer Ordnung (first-class functions) und Funktionsabschlüsse (Closures). Ersteres bedeutet, dass Funktionen selber Werte sind – sie können also als Parameter übergeben und zurückgegeben werden. Letzteres erlaubt den Funktionswerten, dass sie sich den Zustand ihrer Umgebung (die lokalen Variablen) bei ihrer Erzeugung merken können.

Für viele sind Closures seltsame Konstrukte und ihre Einsatzmöglichkeiten nicht offensichtlich. Daher im Folgenden ein kleines, relativ leicht verständliches Beispiel, in dem mehrere Zähler mit individuellen Startwerten erzeugt und benutzt werden:
// closure.fx

function zaehlerMitStartwert(startwert: Integer) {
var n = startwert;
return function(): Integer { return n++ }
}

var next1 = zaehlerMitStartwert(10);
var next2 = zaehlerMitStartwert(20);

println( next1() ); // 10
println( next2() ); // 20
println( next2() ); // 21
println( next1() ); // 11
Wer das Beispiel ausprobieren möchte, installiert sich das JavaFX 1.2 SDK und nutzt die Kommandozeile (oder NetBeans oder Eclipse mit dem JavaFX-Plugin):
Straylight:~ much$ javafxc closure.fx
Straylight:~ much$ javafx closure
10
20
21
11
Die ausgegrauten Elemente sind nicht wirklich notwendig und sollen nur das Skript für Java-Programmierer leichter verständlich machen. "return" ist an beiden Stellen optional, da der letzte Ausdruck in einer Funktion gleichzeitig deren Ergebnis definiert. Der Rückgabetyp ":Integer" der inneren, anonymen Funktion ist optional, da aus dem Datentyp des Ergebnisses der Rückgabetyp durch Typinferenz ermittelt werden kann.

Trotz (bzw. gerade wegen) dieser impliziten Typisierung ist JavaFX Script im Gegensatz zu Skriptsprachen wie JavaScript statisch und streng typisiert. Bereits der Compiler ermittelt also die Datentypen und Signaturen von Variablen und Funktionen, sofern sie nicht explizit angegeben sind. Im Beispiel hat die Variable n also den Datentyp Integer, die Variablen next1 und next2 haben den Typ function():Integer und zaehlerMitStartwert hat den Typ function(:Integer):function():Integer :-)

Wie funktioniert das Skript? Beim Aufruf der Funktion zaehlerMitStartwert wird eine Funktion zurückgegeben, die bei jedem Aufruf die Variable n um eins erhöht zurückgibt. Obwohl die Variable n zur äußeren Funktion gehört und damit beim Aufruf der zurückgegebenen Funktion mit next1() bzw. next2() eigentlich gar nicht mehr vorhanden ist, merkt sich die innere Funktion die lokale Variable separat für jede zurückgegebene innere Funktion – und genau das macht sie zur Closure.

Man kann sich Closures als Objekte vorstellen, die aus einer einzigen Methode/Funktion bestehen und wie Objekte zusätzlich einen Zustand haben. Schön sehen kann man dies an den generierten Java-Bytecode-Klassendateien: closure.class und closure$1.class. Die innere, anonyme Funktion wird für die JVM also einfach als innere, anonyme Java-Klasse closure$1.class abgebildet.

Closures sind prima geeignet, um damit z.B. Event-Handler zu definieren, denen man zusätzliche Informationen mitgeben kann (bei Farb-Setz-Buttons in einem Grafikprogramm beispielsweise die zu setzende Farbe). Dadurch kann man häufig auf das Schreiben von Event-Handler-Klassen verzichten, auch wenn dies in JavaFX Script ebenfalls möglich ist.

Vielleicht hat man es gemerkt: Ich halte JavaFX Script trotz des Namens ganz und gar nicht für eine Skriptsprache. Meines Erachtens wurde die Sprache vor allem deshalb so genannt, um zu zeigen, dass man damit in Wettbewerb zu Skriptsprachen wie Ruby tritt, die als agil und produktiv empfunden werden. JavaFX Script zeigt dabei sehr schön, dass sich Produktivität und statische, strenge Typisierung überhaupt nicht ausschließen müssen.