Die Essenz objektfunktionaler Programmierung und das praktische Potential von Scala

2 Kommentare

Die Begriffe „objektfunktional“ und „objektfunktionale Programmierung“ hört man immer wieder im Kontext der Softwareentwicklung. Aber wie sieht der objektfunktionale Ansatz aus und welche Vorteile hat er? Ist die Objektorientierung oder der funktionale Ansatz allein nicht schon gut genug? Und was hat das alles mit Scala zu tun?

Objektfunktional

Nicht-triviale Software muss zwei Arten von Verhalten realisieren: Sie muss Dinge berechnen und Dinge bewirken. Beispielsweise könnte eine Software berechnen, was als nächstes auf dem Bildschirm auszugeben ist, und diese Ausgabe dann vornehmen. Letzteres lässt sich auch als das Bewirken eines Effekts bezeichnen. Oft werden alle Effekte undifferenziert als Seiteneffekte bezeichnet; es macht aber Sinn zu unterscheiden in essentielle Effekte und in Seiteneffekte. Essentielle Effekte sind Teil der Anforderungen und nicht zu vermeiden: Soll etwas auf dem Bildschirm ausgegeben werden, muss es dort ausgegeben werden. Bei Seiteneffekten handelt es sich hingegen um Effekte, die eigentlich nicht notwendig, nicht interessant oder sogar unerwünscht sind und nur als Artefakt des eigentlich gewünschten Verhaltens auftreten; beispielsweise arbeitet die imperative Programmierung intensiv mit solchen Seiteneffekten, um Berechnungen vorzunehmen.

Objektorientierte Programmierung ist traditionell imperativ geprägt und das Verändern von Objekten und Objektreferenzen, und damit Seiteneffekte, sind ein ständiger Begleiter. Funktionale Programmierung hingegen hat den Schwerpunkt im Transformieren von Werten: Aus Werten werden in der Regel andere Werte berechnet, Werte und Wertereferenzen dabei aber nicht verändert.

Was passiert jetzt, wenn die transformierten Werte Objekte sind? Wir gelangen zu einer möglichen Definition objektfunktionaler Programmierung:

Objektfunktionale Programmierung ist ein Programmieransatz, der das Transformieren von Objekten in den Mittelpunkt stellt und sich hierfür sowohl objektorientierter als auch funktionaler Mittel und Prinzipien bedient. Ein Bewirken von Effekten im Allgemeinen sowie ein Verändern von Objekten und Objektreferenzen im Speziellen erfolgt nur sehr dosiert, für andere Codeteile möglichst transparent und möglichst getrennt von transformativem Code.

Bei der objektfunktionalen Programmierung wird der Software mit Mitteln der Objektorientierung eine zweckmäßige Struktur aufgeprägt, die die lokale Änderbarkeit und Verständlichkeit des Codes fördert; Daten und Verhalten werden zusammengeführt; Zugriffsmodifikatoren werden verwendet, um möglichst nur sinnvolle Abhängigkeiten zuzulassen. Funktionale und viele traditionell eher funktionalen Sprachen zugeordnete Features werden verwendet, um möglichst auf das Verändern von Objekten und Objektreferenzen verzichten und auch im Feingranularen leicht modular bleiben zu können, beispielsweise mit Funktionen als leichtgewichtiger Alternative zum Strategy Pattern. Wie in der funktionalen Programmierung üblich, wird Software in möglichst effektfreie und effektbehaftete Teile aufgeteilt und Effekte werden für andere Codeteile möglichst transparent gestaltet; auf diese Weise sind große Teile des Codes überhaupt nicht von den Nachteilen betroffen, die Effekte beispielsweise für lokale Verständlichkeit und modulare Kombinierbarkeit mit sich bringen.

Anders als bei traditioneller Objektorientierung sind Objekte in der Regel, zumindest von außen betrachtet, unveränderlich; Methoden berechnen normalerweise, bewirken aber keine beobachtbaren Effekte. Anders als bei der funktionalen Programmierung arbeiten Funktionen weniger auf passiven Datenstrukturen; stattdessen treten sie oft in Form von Methoden auf, die zu Objekten bzw. Objekttypen gehören und Zugriff auf deren nicht öffentliche Daten und nicht öffentliches Verhalten haben.

Geeignete Mechanismen werden verwendet, um essentielle Effekte wie beispielsweise gewollte Bildschirmausgaben zu realisieren; diese Mechanismen können recht unterschiedlich ausfallen.

Warum objektfunktional?

Bei der Softwareentwicklung geht es nicht nur darum, das gewünschte Verhalten „irgendwie“ abzubilden. Entwicklung und Weiterentwicklung sollen auch effizient sein und die Qualität der Software hoch. Sowohl die objektorientierte Programmierung als auch die funktionale Programmierung unterstützen dies bis zu einem gewissen Grad, jeder Ansatz auf seine eigene Weise.

Beim objektorientierten Ansatz ist die traditionell imperative Herangehensweise aber problematisch. Positive Eigenschaften, die dieser Ansatz auf die eine Weise fördert, werden hier durch den traditionell imperativen Fokus auf andere Weise wieder konterkariert, insbesondere lokale Verständlichkeit und modulare Kombinierbarkeit. Hinzu kommt, dass veränderbare Objekte nicht ohne weiteres sicher parallel verwendbar sind und der rein objektorientierte Ansatz im Kleinen oft zu schwergewichtig ist. Der funktionale Ansatz glänzt hier hingegen mit leichtgewichtigen Features sowie seiner Minimierung und Isolierung von Effekten.

Der funktionale Ansatz ist wiederum in der Fähigkeit, Systeme strukturieren, Daten abstrahieren und unerwünschte Abhängigkeiten verhindern zu können, weniger gut ausgeprägt. Der objektorientierte Ansatz liefert mit seinen Objekten und dazugehörigen Features hingegen wirksame Antworten.

Dort wo der eine Ansatz stark ist, ist der jeweils andere also schwach. Durch eine Kombination beider Ansätze kann man auch deren Stärken kombinieren und deren jeweilige Schwächen ausgleichen. Man hat dann, potentiell, die Vorteile beider Ansätze ohne deren Nachteile. Wie sehr sich dieses Potential tatsächlich realisieren lässt, hängt auch von der verwendeten Sprache ab.

Scala

Scala ist eine Programmiersprache, die bewusst und von Anfang als objektfunktionale Kombination ausgelegt wurde. Sie bietet, sowohl was die Objektorientierung als auch den funktionalen Aspekt angeht, nicht nur das absolut Nötigste, sondern geht darüber weit hinaus:

  • Objektorientiert: Neben den für eine klassenbasierte Sprache obligatorischen Klassen und (Einfach-) Vererbung gibt es zahlreiche weitere Sprachkonstrukte und Sprachdetails, die es erleichtern, Software sauber zu modularisierten. Mit Traits gibt es beispielsweise eine mächtige Form der Klassenkomposition. Diese erlaubt nicht nur eine Art klassischer Mixins, sondern auch eine an das Decorator Pattern angelehnte Möglichkeit, Klassen schachtelnd aufzubauen. Implizite Klassen erlauben es, Objekte zur Laufzeit bei Bedarf automatisch zu wrappen und Methoden und andere Features zu verwenden, die das ursprüngliche Objekt gar nicht aufweist. Damit lassen sich ad hoc verschiedene erweiterte Sichten auf Objekte realisieren; völlig transparent für die ursprüngliche Klasse und deren Instanziierung und auch ohne Gefahr globaler Konflikte bei sich widersprechenden Sichten. Und Scala bietet in objektorientierter Hinsicht noch einiges mehr.
  • Funktional: Neben den obligatorischen First Class Functions und Closures zeichnet sich Scala durch viele weitere Sprachkonstrukte und Sprachdetails aus, die es erleichtern, auch großflächig ohne das Verändern von Objekten und Objektreferenzen auszukommen. Hierzu gehören beispielsweise das Pattern Matching und For Comprehensions. Mit Funktionen, Closures und Tupeln hat man zudem im Kleinen eine leichtgewichtige Alternative zum Definieren eigener Klassen. Wie jede funktionale Sprache, die praktikabel einsetzbar sein soll, muss auch Scala Möglichkeiten bieten, essentielle Effekte direkt oder indirekt zu bewirken. Scala bietet für diesen Zweck pragmatisch die bekannten, direkten Mittel imperativer Programmierung, beispielsweise Zuweisungen.

Die objektorientierten und funktionalen Features sind in Scala dabei auf eine Weise gestaltet, die kompatibel zu den Ursprungsansätzen ist und eine integrierte Verwendung praktikabel und lohnenswert macht. Beispielsweise kann man Objekte nicht nur funktional pattern matchen, dies ist auch objektorientiert möglich, ohne das Prinzip des Information Hidings zu verletzen; und objektorientierte Methoden lassen sich beispielsweise nicht nur konzeptionell als Spezialfall von Funktionen auffassen, sondern oftmals auch technisch leicht als solche verwenden. Scalas imperative Anteile können auch genutzt werden, um Code in Einzelfällen performanter und klarer zu gestalten; sehr dosiert, für andere Codeteile möglichst transparent und von transformativem Code möglichst getrennt eingesetzt, erhöhen diese imperativen Anteile die Praktikabilität der objektfunktionalen Programmierung, statt diesem Ansatz zuwiderzulaufen. Abgerundet wird das ganze mit kompakter Syntax und fortschrittlicher statischer Typisierung, mit denen sich objektfunktionaler Code in der Regel sehr kurz, übersichtlich und doch statisch typsicher formulieren lässt.

Aber was ist mit der Standardbibliothek? Das alles wäre schön, aber verbesserungswürdig, wenn die Standardbibliothek objektfunktionale Basisabstraktionen, insbesondere unveränderliche Collections, vermissen lassen würde. Aber auch hier wird man nicht enttäuscht. Die Standardbibliothek liefert wichtige objektfunktionale Basisabstraktionen mit, insbesondere unveränderliche Collections; und andere Scala APIs erwarten und liefern normalerweise Instanzen dieser Basisabstraktionen.

Fazit

Objektfunktionale Programmierung ist die natürliche Verbindung zweier kompatibler Programmieransätze. Dieser kombinierte Ansatz verbindet die Vorteile beider Ursprungsansätze und vermeidet deren Nachteile. Unter dem Strich ergeben sich gegenüber den einzelnen Ansätzen eine potentiell höhere Produktivität und Softwarequalität.

Inwieweit dies in der Praxis realisiert werden kann, hängt unter anderem von der verwendeten Sprache ab. Scala ist eine Programmiersprache, bei der das Kreuzen des objektorientierten und des funktionales Ansatzes Leitidee war. Entsprechend gut ist die Sprache zur objektfunktionalen Programmierung geeignet.

Es lohnt sich also, sich weiter mit der objektfunktionalen Programmierung im Allgemeinen und mit Scala im Speziellen zu beschäftigen. Und bei allen Detailfragen und vorübergehenden Unwägbarkeiten stets im Hinterkopf zu behalten, worum es beim objektfunktionalem Ansatz wirklich geht: Höhere Produktivität und Qualität zu erreichen, indem man das Transformieren von Objekten in den Mittelpunkt stellt.

Kommentare

  • 31. August 2015 von Stefan Zörner

    Hallo Martin,

    vielen Dank für den schönen Beitrag. Das Wort objektfunktional höre ich gar nicht so oft, aber das hat immerhin dazu geführt, dass ich Deinen Artikel gelesen habe … 😉

    Eine Frage hätte ich allerdings: Du schreibst: „… beispielsweise arbeitet die imperative Programmierung intensiv mit solchen Seiteneffekten, um Berechnungen vorzunehmen.“

    Kannst Du da ein Beispiel nennen? In C z.B. …

    Herzliche Grüße,
    StefanZ

    • Martin Lau

      1. September 2015 von Martin Lau

      Hallo Stefan,

      hier ein Beispiel für eine Java-Methode, die mit Hilfe eines Seiteneffektes eine Berechnung vornimmt:

      public static BigInteger sum(List<BigInteger> summands) {BigInteger sum = BigInteger.ZERO;
       
        for(BigInteger s : summands) { 
      		
          sum = sum.add(s);
        }

    	
       
        return sum;}

      Dieses Beispiel ist noch vergleichsweise harmlos. Allerdings berechnen wir hier auch nur eine simple Summe von BigIntegers, der Seiteneffekt ist lokal und wir verändern zwar eine Objektreferenz aber keine Objekte.

      Wir müssen uns aber bereits hier fragen, wie wir mit potentiellen nicht-lokalen Seiteneffekten anderen Codes umgehen: Die reingereichte Liste summands ist potentiell veränderbar und kann theoretisch jederzeit von außen verändert werden. Deshalb und auch wenn wir komplexere Berechnungen vornehmen, wir nicht nur Objektreferenzen sondern auch den Zustand von Objekten verändern, mit komplexeren Objekten als BigIntegers arbeiten und auch selbst nicht-lokale Seiteneffekte bewirken, ist es mit der Harmlosigkeit sehr schnell vorbei.

      Beste Grüße
      Martin Lau

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.