Funktionale Sprachen erhalten ihre Eleganz dadurch, dass sie die Programmierung mit Funktionen als Argumente und Rückgabewerte erlauben. Schön erklärt wird das im SICP. Tatsächlich kann das so weit gehen, dass zum Beispiel in Lisp gar kein Unterschied mehr zwischen Funktionen und beliebigen Daten gemacht wird. Aber das ist zugegebenermaßen extrem.
Für die Kürze nennt man diese Art zu programmieren auch gerne: "Higher-Order-Programmierung" und die entsprechenden Funktionen "Higher-Order-Funktionen".
In der Imperativen Programmierung werden Funktionen als Argumente oder Rückgabetypen eher selten verwendet. In C gerade noch beim Standard-Algorithmus Binary-Search
void * bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compare) (const void *, const void *));
bei dem die compare-Funktion genau so eine Higher-Order-Funktion ist. Danach ist es aber erstmal lange zappenduster, was Higher-Order-Programmierung angeht. (Hervorragende Programmierer nutzten dieses Design-Pattern aber schon immer - wenn es auch selten explizit genannt wurde)
So kam es dann auch, dass manche objektorientierten Sprachen Objekte wie Knöpfe, Textfelder etc. hatten, die man ableiten konnte, um ihr Verhalten zu verändern. Java etwa erlaubt das noch heute:
// Java class OKButtonForThatSpecificDialog extends JButton { public void doClick() { System.out.println("Yay, I was clicked"); } }
Zwei Probleme ergeben sich aus dieser Art zu programmieren:
- Erstens: jede Menge duplizierter Code, der größtenteils identisch ist. Damit erlebt man einen Alptraum, sobald man auch nur daran denkt, etwas Grundlegenderes an dem Programm zu ändern.
- Zweitens ist der Code, der tatsächlich die Kontrolle über das Programm ausführt, so in viele Dateien oder zumindest Klassen verstreut, also mitnichten an einer Stelle vereint und übersichtlich.
Beide Probleme kann man mit dem oben beschriebenen Mechanismus der Higher-Order-Funktionen elegant lösen - und tatsächlich wird das auch gemacht. In Inversion of Controll (IOC)-Frameworks etwa. Hier ruft man nicht mehr eine Funktion auf, um ein Ergebnis zu bekommen, sondern konfiguriert den Framework (Knöpfe, Textfelder, etc.) mit eigenen (Higher-Order)-Funktionen, die dieser aufruft, sobald ein Ergebnis oder Ereignis vorliegt.
Absolut typisch für dieses Muster sind alle mir bekannten aktuell verwendeten GUI-Toolkits, die immer so funktionieren, dass man z.B. einen Knopf erzeugt, um danach mit einer (Higher-Order)-Funktion zu konfigurieren, was denn passieren soll, wenn auf ihn geklickt wird.
// Java JButton button = new JButton("Titel"); button.addActionListener(aClickResponder);
// Obj-C [aButton setTarget: self]; [aButton setAction: @selector(respondToClick:)];
Aber dieses Muster ist noch viel weiter verwendbar - in Objective-C gibt es eine Möglichkeit, auf alle Elemente eines Containers eine Methode doSomethingWithEachElement: anzuwenden, die ein Argument - das jeweilige Element - erhält.
// Obj-C [aList makeObjectsPerformSelector:@selector(doSomethingWithEachElement:)];
Äquivalent läuft das in den GUI-Toolkits von Java, Cocoa, Gnome, KDE, WxWidgets...
Fortgeschrittenere Sprachen und Toolkits verwenden dieses Design-Pattern auch noch an vielen weiteren Stellen. Besonders hervorzuheben ist dabei in letzter Zeit Ruby, wo nach diesem Muster nicht nur Iteration, sondern auch das Bearbeiten jeder Zeile in einer Datei, das Senden von Daten über einen Socket, ... verläuft.
Am extremsten ist aber vielleicht die Sprache Smalltalk, wo es überhaupt keine normalen Kontrollstrukturen im Sinne von C/Java mehr gibt und diese Aufgabe komplett durch Higher-Order-Funktionen erledigt wird. Hier gibt es beispielsweise ein Boolean Objekt, das eine Methode #ifTrue:ifFalse:
besitzt. Diese Methode bekommt dann zwei Callbacks übergeben und führt je nachdem, ob es das True- oder das False-Boolean ist, polymorph den jeweils anderen Callback aus. - Analog werden Schleifen, Exception-Handling... einfach alle Kontrollstrukturen definiert.
Das nette daran: Damit kann man innerhalb der Sprache selber Kontrollstrukturen definieren. Schick, nicht?
Umsetzung
Um mit Higher-Order-Funktionen in Java / Obj-C zu programmieren, kann man z.B. so vorgehen:
// Obj-C - (void) setAction: (SEL) aSelector forTarget: aTarget { action = aSelector; target = aTarget; } - (void) trigger { if ([target respondsToSelector: action]) { [target performSelector: action]; } }
// Java void setActionListener(ActionListener listener) { this.listener = listener; } void trigger() { listener.actionPerformed(null); }
Das wars - so einfach geht das.
TODO: Auf [http://libsigc.sourceforge.net/libsigc2/docs/manual/html/ch02.html] verweisen und vergleichen wie Callbacks mit sowas und (beste C++ Callback Bibliothek) gehen. Vergleichen wie echte Lambdas das leichter machen können.