Closures und „erstklassige“ Funktionen


Heute soll es um ein grundlegendes Feature von Scala und schwerwiegendes Manko von Java gehen: Funktionen erster Klasse (engl.: „First Class Functions“). Schon bei diesem Satz ist die Verwirrung programmiert, deshalb wollen wir an dieser Stelle erst einmal sehr sorgfältig definieren, worüber wir eigentlich sprechen, den an wenigen Stellen der Mainstream-Programmierung ist die Sicht so trübe wie im Dunstkreis der Begriffe „Closure“, „Funktion“ und „Methode“. Natürlich sind Object.hashCode() oder Math.sin(double x) im mathematischen Sinne „Funktionen“, aber aus Java-Sicht handelt es sich um Methoden, denn sie sind an ein Objekt bzw. eine Klasse gebunden. Auch Scala hat derartige Methoden. Unter „Funktionen erster Klasse“ versteht man dagegen Funktionen, die selbstständige und vollwertige Bestandteile der Sprache sind: Sie besitzen einen eigenen Typ, und man kann sie Variablen zuweisen oder als Argumente übergeben. So etwas geht in Java definitiv nicht, denn dort ist die einzige grundlegende Einheit das Objekt, und so muss man mit Interfaces und anonymen Klassen tricksen, um z. B. Funktionen als Argumente übergeben zu können (man denke etwa an das Runnable-Interface). Scala hat Funktionen erster Klasse, man kann sie zuweisen und übergeben, man kann sie sehr bequem (und mit jeder Menge syntaktischem Zucker) erzeugen und auch ohne großen Aufwand aus einer Methode eine Funktion machen. In diesem Zusammenhang spricht man auch von „Funktionen höherer Ordnung“ (engl.: „Higher Order Functions“), d.h. Funktionen, die andere Funktionen als Argumente nehmen und/oder zurückliefern. Ein kleines Beispiel:

def twice[A](func: A => A)(a:A) = func(func(a))

def square(n:Int) = n*n

val biSquare = twice(square) _

biSquare(3)
//--> res0: Int = 81
twice(biSquare)(2)
//--> res1: Int = 65536

Die Funktion twice nimmt eine Funktion als Argument, bei der Argumenttyp und Rückgabetyp gleich ist, und führt sie „auf sich selbst“ aus. Aus sin(x) wird also sin(sin(x)) oder in unserem Fall aus square(x) die Funktion square(square(x)), die also „insgesamt“ die vierte Potenz berechnet. Die zurückgegebene Funktion lässt sich in einer Variablen speichern (man beachte den Unterstrich, der dafür sorgt, dass die Funktion nicht ausgeführt wird, sonder „als Ding an sich“ übergeben wird), ausführen oder auch nochmal an twice übergeben, was eine Funktion liefert, die die sechzehnte Potenz berechnet.

Man stelle sich vor, man solle in Java etwas Analoges zu twice schreiben – das wäre eine ziemliche Herausforderung. Nun taugt diese Form der Abstraktion nicht nur für solche kleinen Spielchen, sondern ist auch extrem nützlich, um flexible, abstrakte Algorithmen zu schreiben. Zum Beispiel ist es Sortieralgorithmen völlig egal, nach welchen Kriterien die Elemente miteinander verglichen werden, solange es nur „den Regeln“ entspricht (hier: solange eine totale Ordnung hergestellt wird), und Funktionen erster Klasse erlauben, dieses Detail elegant zu abstrahieren.

Aber schauen wir und erst einmal die Alternative in Java an: Ein Beispiel von www.java2s.com, leicht aktualisiert:

public class EmpComparator implements Comparator<Person> {
  public int compare(Person emp1 , Person emp2) {
    int nameComp = emp1.getFirstName().compareTo(emp2.getFirstName());
    return ((nameComp == 0) ? emp1.getLastName().compareTo(
        emp2.getLastName()) : nameComp);
  }
}

Das ist eine ganz gewöhnliche Comparator-Klasse, die man Collections mitgeben kann, um die Sortierreihenfolge festzulegen – und damit das täglich Brot eines Java-Programmierers. Nun ja, das mag ja eventuell angehen, wenn man einen Comparator wiederverwendet, aber wie oft will man einfach nur eine Liste sortieren und verwendet die tolle Comparator-Klasse nie wieder?

Hier eine mögliche Scala-Lösung. Da die Bezeichner firstName und lastName unangenehm lang sind, habe ich ein wenig in Scalas Trickkiste gegriffen: Wenn man Person als Fall-Klasse schreibt (was für derartige Wert-Klassen sowieso die beste Lösung ist), kann man selbige elegant mit einem case „auseinandernehmen“ (kleines Detail: case muss in geschweiften Klammern stehen, und dann darf man die umschließenden runden Klammern weglassen).

case class Person(firstName:String, lastName:String)

List(Person("Werner","Heisenberg"),Person("Albert","Einstein"),Person("Max","Planck")).sort{
    case (Person(f1,l1),Person(f2,l2)) => if(f1 == f2) l1 < l2 else f1 < f2 }
&#91;/sourcecode&#93;

Ja, die Java-Lösung funktioniert auch. Aber ich denke, die Syntax macht hier einen gewaltigen Unterschied. Und sobald man Java mit den Möglichkeiten von Funktionen erster Klasse im Hinterkopf betrachtet, fallen einem sofort die ganzen hässlichen Krücken auf: Comperatoren, Iteratoren, Runnable, ActionListener und wie sie alle heißen. Ein kurzer Blick in die Bibliothek <a href="http://functionaljava.org/" target="_blank">Functional Java</a> zeigt, was mit "echten" Funktionen alles möglich wäre, denn sie besitzt sowohl eine API für normales Java wie auch für Java mit <a href="http://javac.info/" target="_blank">BGGA</a>. 

Kommen wir zum Begriff "Closure", der oft synonym zu "Funktion erster Klasse" gebraucht wird. Das stimmt nicht ganz, denn nicht jede Implementierung einer "Funktion erster Klasse" muss sich auch als Closure verwenden lassen (so sind <a href="http://www.newty.de/fpt/index.html" target="_blank">Funktionspointer in C/C++</a> keine Closures). Allerdings sind beide Konzepte so eng miteinander verwandt, dass sie in vielen Sprachen - so auch Scala - zu einem einzigen Konstrukt zusammengefasst werden, was zwar zu einer einfacheren Sprache, aber nicht unbedingt zu einer klareren Definition beiträgt. Was ist nun eine Closure? Der Name "Closure" kommt daher, dass Closures ihre Ursprungs-Umgebung wie eine Kapsel "umschließen" und mit sich herumtragen (eine detailliertere Beschreibung findet sich bei <a href="http://de.wikipedia.org/wiki/Closure" target="_blank">Wikipedia</a>). Klingt das etwas seltsam? Dann spielen wir doch einmal Pingpong mit einer lokalen Variablen, deren Definitionsbereich schon lange verlassen wurde:


def funny = {
  var x = 0 
  ( () => {x+=1; x}, () => {x+=10; x})
}
//--> funny: (() => Int, () => Int)

val (c1,c2) = funny
//--> c1: () => Int = <function>
//--> c2: () => Int = <function>

println(c1())
//--> 1
println(c2())
//--> 11
println(c2())
//--> 21
println(c1())
//--> 22

Ein Erklärungsversuch: funny ist eine Methode, die eine lokale Variable x enthält und zwei Closures zurückliefert, die x um eins oder zehn erhöhen. Der Witz ist, dass beide Closures Zugriff auf dasselbe x besitzen und die Änderungen der „anderen Seite“ sehen können.

Was hier mächtig nach Spielerei aussieht, ist in Wahrheit ein mächtiges Werkzeug. Die englische Version des oben genannten Wikipedia-Artikels listet folgende Anwendungsgebiete auf: Steuerung des Verhaltens von Bibliotheks-Funktionen durch Übergabe von Closures (unser sort-Beispiel), Definition von Kontroll-Strukturen, Kommunikation über Veränderung der Umgebung (unser funny-Beispiel) und – nur für funktionale Sprachen interessant – die Implementierung eines Objekt-Systems (etwa in Bla). Closures können also tatsächlich mehr als irgendwelche Klassen-Ersatzversuche.

Tja, was ist denn nun mit Java? Java ist die einzige Mainstream-Programmiersprache, die keine Closures (wie Boo, C#, Delphi, Fan, Groovy, JavaScript, Lua, PHP, Perl, Python, Ruby, Scala oder Smalltalk) oder zumindest Funktionen erster Klasse (wie C/C++ mit seinen Funktionspointern) besitzt.
Und wie sich mittlerweile sicher herumgesprochen hat, wird Java 7 leider keinen der Vorschläge (wie die bereits oben genannte BGGA-Implementierung) für die Unterstützung von Closures umsetzen. Meiner Meinung nach ist damit der Zug für Java endgültig abgefahren. Natürlich wird die Sprache allein wegen der ungeheuren Menge vorhandenen Codes weiterexistieren (das tut COBOL ja auch), aber ich kann mir nicht vorstellen, wie es nach dieser Entscheidung in absehbarer Zeit in Javaland Innovation geben könnte, die diesen Namen verdient. Und Java braucht wirklich Innovation, die über syntaktischen Zucker und Trostpflästerchen hinausgeht.

Schade.

Advertisements

2 Gedanken zu “Closures und „erstklassige“ Funktionen

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s