Wenn die Liste der funktionalen Sprachen erstellt wird, dann tauchen Haskell, Scala, Erlang, F#, Clojure und einige andere Lisp-Varianten auf.
Wenn man sich anschaut, welche Merkmale funktionale Sprachen auszeichnen, dann stellt sich die Frage, ob das nicht alles mit Ruby auch machbar ist.
Schauen wir einmal was man so typischerweise findet, meist auf Englisch:
- Functions as „first class citizen“
- Closures
- Pure Functions
- Higher Order functions
- Everything returns a value
- Immutability
- No hidden state
- Prefer recursion over iteration (immutable loop variable…)
Funktionen existieren in Ruby also losgelöste Objekte, in verschiedenen Formen, z.B. als proc, als lambda, als anonymer Block und durch Referenzierung einer Methode über ihren Namen (Reflection).
Closures werden von Ruby problemlos unterstützt, bei all diesen Formen der Funktionen werden Variablen aus dem definierenden Kontext eingebunden, wo sie referenziert werden. Methoden kann man übrigens als Spezialfall von Closures ansehen, weil sie als Kontext das Objekt einbinden, zu dem sie gehören.
Unter „pure function“ versteht man Funktionen, die den Funktionen aus der Mathematik entsprechen. Sie haben absolut keinen Seiteneffekt, sind reproduzierbar, können in beliebiger Zahl parallel ausgeführt werden und geben bei mehrfachen Aufrufen immer dasselbe Resultat. Sie eignen sich für Memoize, was nun eigentlich wieder ein Seiteneffekt ist, aber sozusagen ein transparenter. Man denke an sort() und sort!() in Ruby. Dies verlangt Disziplin vom Entwickler und gute Dokumentation.
„Higher Order Functions“ sind Funktionen höherer Ordnung, die also selbst Funktionen als Parameter oder Rückgabewert haben. Das ist in der funktionalen Programmierung Routine und nicht so ein spezieller Spezialfall, den man mal alle paar Jahre benutzt, wie Funktionspointer in C. Typische Beispiele sind Methoden wie inject(), map(), group_by()… each() sollte man nur verwenden, wenn man die Seiteneffekte wirklich braucht.
Alle Ausdrücke haben einen Wert. Das ist recht gut in Ruby umgesetzt, z.B. x = if (...) ... else ... end
kann man verwenden…
Immutibility (Unveränderbarkeit) ist die Achillesferse. Es wird nicht wirklich gut unterstützt. Man soll Variablen nicht neu zuweisen und auch nicht irgendwas mutieren. Warum nimmt man nicht freeze()? Wir brauchen ein deepfreeze(), aber was bedeutet das? Wie sieht es mit collections aus? Es gibt immer Möglichkeiten, diese unverändert zu lassen und mit jedem Schritt eine neue Collection zu produzieren. Dasselbe gilt für Zeichenketten.
„No hidden State“, also kein versteckter Zustand. Man kann Kontextobjekte haben und herumreichen. Zustand ist unerwünscht und sollte kontrolliert an wenigen Orten gehandhabt werden.
„Recursion instead of Iteration“ steht für Rekursion statt Iteration. Funktionale Sprachen unterstützen Optimierung bei Tailrekursion (Endrekursion). Einige andere Sprachen, z.B. C mit gcc, Scala und viele Lisp-Dialekte, aber nicht Java, unterstützen diese Optimierung und erlauben es, zumindest Endrekursion ohne zu große Furcht einzusetzen. Bei Ruby hängt es von der Version und den Einstellungen ab, ist also mit Vorsicht zu genießen. Der Ruby-Ansatz ist es eher, die Iteratorn zu verwenden.
Fazit: Man kann mit ein paar Einschränkungen in Ruby funktional programmieren, aber es erfordert etwas mehr Disziplin, weil einige Dinge vom Entwickler beachtet werden müssen und nicht von der Sprache unterstützt werden.
Schreibe einen Kommentar