Seit einiger Zeit beschäftige ich mich mit funktionaler Programmierung in der Gewissheit, dass die darin angewandten Konzepte das Programmieren effizienter, sicherer und schöner machen können.
Ein in der funktionalen Programmierung wichtiges Konzept – das dennoch nicht von allen funktionalen Sprachen angeboten wird – ist das Konzept der faulen Auswertung (lazy evaluation).
Diese Faulheit kann man an zwei Stellen antreffen: Zum einen beim Funktionsaufruf – dann bedeutet sie, dass die Funktionsargumente nicht beim Aufrufer ausgewertet werden sondern in der Funktion selbst und auch nur dann, wenn sie benötigt werden. Man spricht hier von “normaler Auswertungsreihenfolge” oder “nicht strikter Auswertung” – im Gegensatz zur “applikativen Auswertungsreihenfolge” oder “strikten Auswertung”.
Ein – wie ich finde – gutes Beispiel ist Logging:
In der Anleitung zu log4j (einem Logging-Framework für Java) findet man, dass das Aufrufen der Logmethoden auch kostet, wenn gar nicht geloggt werden soll (das ist eigentlich klar – schießlich ist ein Javaprogrammierer und im Besonderen Java selbst nicht faul, sondern übereifrig): wenn man im Level “Error” ist und im Code sich Aufrufe im Level “Debug” befinden, so werden die Argumente (meist Stringkonketanationen von Anwendungsdaten) zu den Logmehtoden immer ausgewertet – auch wenn dann innerhalb der Methode festgestellt wird, dass das Logging aufgrund des Levels gar nicht durchgeführt werden soll. Dann ist’s schon zu spät und vieles umsonst gemacht.
Im Manual wird als Lösung angeraten, vor jedem Logaufruf eine if-Abfrage auf den Loglevel zu machen, um unnötige Stringerzeugungen zu verhindern.
Das führt nicht gerade zu schönerem, lesbarerem Code, oder?
Neben der Faulheit bei Funktionsaufrufen gibt es zum Zweiten faule Datenstrukturen – Streams. Man stelle sie sich als Listen vor, deren Bestandteile erst beim Traversieren erzeugt (realisiert) werden.
Einfaches Beispiel: die eifrige (eager) Auswertung der Liste der natürlichen Zahlen ist eine schlechte Idee – sie kann recht lange dauern.
Wenn man die Liste der natürlichen Zahlen jedoch faul auswertet, so werden die Zahlen erst dann bereitgestellt, wenn sie benötigt werden. So ist es beispielsweise möglich, die ersten 1000 Zahlen aus einer solch faulen Liste zu nehmen.
In seinem Blogeintrag “Side Effects and Functional Programming” führt Matthew Podswysocki folgende Stolpersteine beim Aufeinandertreffen von Faulheit und Seiteneffekten auf:
- purity
- exception handling
- resource management
- closures
Wirklich mit Seiteneffekten hat aber nur der erste Punkt zu tun: purity, d.h. seiteneffektfreie Funktionen.
Hier entsteht ein Problem, wenn man sich auf die Reihenfolge verlässt, in der Seiteneffekte bei der Abarbeitung einer Liste (Streams) entstehen – bzw. wenn man sich darauf verlässt, dass sie überhaupt entstehen. Im Blogeintrag von Herrn Podswysocki sind das Print-Ausgaben – stellvertretend für irgendwelche anderen IO-Aktionen oder Zustandsänderungen.
Die Exception- und Ressourcenproblematik hat nicht unbedingt mit Seiteneffekten zu tun, sondern weisst auf allgemeine Stolpersteine auf dem Weg zur grenzenlosen Faulheit hin:
Wenn das Versprechen, das durch die faule Struktur gegeben wurde, realisiert wird, dann sind die Exceptionhandler nicht mehr gültig und Fehler rauschen durch – oder – Dateien sind bereits wieder geschlossen und man kann nicht mehr aus ihnen lesen.
Die Closureproblematik ist eher ein Fehler in der Implementierung von Closures in C# – in korrekten Funktionalen Sprachen (F#, Haskell, Clojure) tritt das Problem nicht auf.
Resume und Schluß
Faulheit bietet interessante neue und anregende Möglichkeiten zur Problemlösungsgestaltung – einiges sieht schöner aus und fühlt sich besser/natürlicher an als vielleicht gewohnt.
Anstatt hier einen coolen Codeschnipsel zu präsentieren verweise ich lieber beispielhaft und abschließend auf die sehr guten Beispiele in Living Lazy, Without Variables.
Wenn man davon absieht, dass man den faulen Clojure-Code nicht auf anhieb versteht (Übungssache!), muss man doch sofort anerkennen, dass er besser (weil kürzer) aussieht und ‘reiner’ ist – im imperativen Codebeispiel ist einfach zu viel Code ‘drumerhum’ (sogenannter boilerplate-code).
Wenn man Seiteneffekte vermeidet – was sich sowieso vernünftigerweise anbietet (dazu in einem anderen Beitrag sicherlich bald mehr) – und wenige Punkte beim Exception- und Ressourcenmanagement beachtet, dann ist Faulheit gar nicht schwer.
Viel Spaß beim Faulenzen!
Post a Comment