Kapitel 1. Was ist funktionale Programmierung?

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Funktionale Programmierung? Funktoren? Monoide? Monaden? "Ich bin kein Mathematiker!", wirst du vielleicht sagen. Wie kann ich diese esoterischen Konzepte lernen? Und warum sollte ich das wollen? Diese Bedenken sind absolut verständlich. Aber die Wahrheit ist, dass du kein Mathematiker sein musst, um ein funktionaler Programmierer zu sein.

Die grundlegenden Konzepte von FP sind leicht zu verstehen, wenn sie auf eine klare und einfache Weise präsentiert werden. Und genau darum geht es in diesem Buch. FP verständlich und praktisch zu machen. Vor allem werde ich dir beibringen, wie ein funktionaler Programmierer zu denken. Aber warum solltest du FP lernen wollen?

Stell dir Folgendes vor. Es ist 22 Uhr und du steckst völlig fest, während du versuchst, einen Fehler in einem Programm zu beheben, das du am Morgen einreichen musst. Das Problem scheint sich um eine Variable namens ratio zu drehen. Das Problem ist, dass sich die Variable Ratio je nach Zustand des Systems, das du modellierst, ständig ändert. Deine Frustration nimmt zu. Oder du hast einen Abgabetermin auf der Arbeit und es gibt einen schwer fassbaren Fehler in deinem Microservice, dem du nachjagst. Das Problem scheint in zwei verschachtelten for-Schleifen zu liegen, in denen die Variablen auf ziemlich komplexe Weise verändert werden. Die Logik ist komplex und du siehst die Lösung nicht ganz. Wenn es doch nur einen Weg gäbe, Programme so zu schreiben, dass sich der Wert der Variablen nicht ändert! FP ist die Rettung.

Hinweis

Variablen, deren Werte sich häufig ändern, sind eine beträchtliche Quelle für Fehler in Programmen. Es kann schwierig sein, den Wert der Variablen im Auge zu behalten, da er sich jeden Moment ändern kann.

Was also ist FP? Was macht eine Sprache funktional und eine andere nicht funktional? Die Wahrheit ist, dass es bis zu einem gewissen Grad eine Frage des Grades ist. Du musst nicht jedes Prinzip befolgen, das unter den Begriff FP fällt. Manche werden versuchen, alle zu befolgen, andere werden sich etwas aussuchen. Das ist ganz dir überlassen. FP ist ein Paradigma, eine Herangehensweise an das Programmieren, eine Art, die Welt zu zerlegen und sie in Code wieder zusammenzusetzen. Dabei geht es sowohl darum, wie wir den Teil der Welt, den wir modellieren, organisieren als auch darum, wie wir den Code organisieren und strukturieren.

Um das Wesen der FP besser zu beschreiben, wollen wir sie zunächst der imperativen Programmierung und der objektorientierten Programmierung (OOP) gegenüberstellen. Es gibt noch andere, wie z. B. die logische Programmierung, aber die drei genannten sind bei weitem die beliebtesten.

Der Imperativ ist das, was du unter vielleicht als einfache alte Programmierung verstehst. Es ist das, was Programmierung vor OOP und FP war. Bei der imperativen Programmierung schreibst du Funktionen oder Prozeduren, verwendest for und while Schleifen und veränderst häufig den Zustand. Sprachen wie C oder Pascal sind typische imperative Programmiersprachen. Dann gibt es noch OOP. Das derzeit beliebteste Paradigma, OOP, ist ein Prozess , bei dem die Welt als eine Sammlung von Objekten modelliert wird. Jedes Objekt hat einen Zustand und Methoden, d.h. Operationen, die ein bestimmtes Verhalten für dieses Objekt darstellen. Während das Programm läuft, ändert sich der Zustand der Objekte. Zu den Vorteilen dieses Ansatzes gehört die Kapselung, d. h. der Zustand und die Methoden, die zu einem Objekt gehören, befinden sich auf der Codeebene innerhalb des Objekts. Das ist eine viel bessere Idee, als den Zustand über den gesamten Code verstreut zu lassen, denn die Verwaltung des veränderlichen Zustands ist einfach schwierig. Du hast mehrere Variablen und ihre Werte ändern sich. Der Ansatz von FP besteht darin, dies anzuerkennen und zu versuchen, Zustandsänderungen zu minimieren, wenn nicht sogar ganz zu vermeiden.

Hinweis

Ein grundlegendes Prinzip der RP ist es, den sich verändernden Zustand zu beseitigen, anstatt zu versuchen, ihn zu verwalten.

Letztendlich ist es nicht immer möglich, veränderliche Zustände zu vermeiden. Daher besteht der Standardansatz von FP darin, den Teil des Codes zu isolieren, der Zustandsänderungen vornimmt. Wenn es nicht möglich ist, alle Zustandsänderungen zu beseitigen, können wir zumindest den Code mit den Zustandsänderungen an einer Stelle lokalisieren.

Unveränderlichkeit

Der wichtigste Aspekt von FP ist die Unveränderlichkeit. Im Allgemeinen bedeutet dies, dass etwas nicht verändert werden kann. Etwas gilt als unveränderlich, wenn wir es nicht in irgendeiner Weise verändern können. In FP bedeutet das ein paar Dinge. Sobald eine Variable gesetzt ist, kann ihr Wert nicht mehr geändert werden. Wenn x = 3 am Anfang eines Programms ist, hat sie diesen Wert für den Rest des Programms. Heißt das, wenn ein Programm im funktionalen Stil geschrieben ist und sich das Alter einer Person ändert, kann diese Änderung nicht modelliert werden? Nein, natürlich nicht. Das wäre absurd. Es gibt Techniken wie das effiziente Kopieren, mit denen wir unseren Code manipulieren können, ohne dass sich der Zustand ändert. Betrachte die folgende einfache for Schleife in Java, die die Zahlen 0 bis 99 ausgibt.

Java

for (int i = 0; i < 100; i++) {
    System.out.println( i );
}

Diese Art von Code kommt auf ständig vor. Du fragst dich vielleicht, wie wir das auf unveränderliche Weise ausdrücken können. Ein gängiger Ansatz in FP ist die Verwendung rekursiver Funktionen; eine rekursive Funktion ist eine Funktion, die sich selbst aufruft. Im Fall des vorangegangenen Codes kannst du den Code in eine Funktion packen und die Funktion dann bei jeder Iteration auf den nächsten Wert von i aufrufen. Das könnte etwa wie folgt aussehen:

Java

void f(int i) {
    if (i > 99) {
        return;
    }
    else {
        System.out.println( i)
        return f(i+1)
    }
}

f(0)

Dieser Code ist zwar etwas länger, aber er verändert keinen Zustand. Wenn du dich ein wenig mit FP auskennst, weißt du vielleicht , dass der Rückgabetyp void ein sicheres Zeichen dafür ist, dass es Seiteneffekte geben wird.1 Ein Seiteneffekt ist alles, was das Programm außerhalb der Funktion beeinflusst. Zum Beispiel das Schreiben in eine Datei, das Auslösen einer Ausnahme oder das Ändern einer globalen Variable. Das Codebeispiel von vorhin soll eine Möglichkeit aufzeigen, wie du die Veränderung des Zustands vermeiden kannst. Wahrscheinlich hast du in deiner ganzen Programmierkarriere Zustandsänderungen vorgenommen, die dir wahrscheinlich unverzichtbar erscheinen. Aber erinnere dich an zwei Dinge:

  • Es fühlt sich sehr natürlich an, den Zustand zu mutieren

  • Mutierende Zustände sind eine der Hauptursachen für komplexe Codes

Die gute Nachricht ist, dass sich der FP-Weg mit etwas Übung genauso natürlich anfühlen wird.

Betrachten wir eine andere Technik, mit der die Mutation des Zustands vermeidet. Stell dir vor, du hast ein Objekt mit einer Eigenschaft oder einem Feld, das sich ändert. Die Frage ist nun, wie du diese Situation modellieren kannst, ohne dass eine Variable im Code verändert wird. Betrachten wir zunächst ein Java-Beispiel.

Java

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public static void main(String[] args)  {
        Person person = new Person("Carl", 32);
        //A year passes
        Person changedPerson = new Person("Carl", 33);  1
        System.out.println(changedPerson);
    }
}
1

Anstatt den Wert von age im Person Objekt zu ändern, erstellen wir ein neues Objekt und initialisieren den neuen Wert von age im Konstruktor.

Sehen wir uns nun ein Beispiel in Python an.

Python

class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def main():
        person = Person("John",22)
	#One year later
	changedPerson = Person("John",23)

Ein Jahr vergeht und wir brauchen das Person Objekt, um dies widerzuspiegeln. Aber wir können den Wert age nicht ändern. Also erstellen wir ein weiteres unveränderliches Objekt mit der Variable age, die auf 23 initialisiert ist.

Sehen wir uns nun ein Beispiel in Scala an.

Scala

case class Person(name: String, age: Int) 1
val person = Person("Katherine", 25)      2
val changedPerson = person.copy(age=26) 3
1

Deklariere eine case Klasse.

2

Erstelle eine Instanz der Klasse.

3

Diese Zeile erzeugt eine neue Instanz von Person und initialisiert age auf 26. Es wurde kein Zustand verändert.

Unveränderlichkeit ist einer der wichtigsten Aspekte von FP. Viele veränderliche Zustände in einem Programm führen zu einer Vielzahl von Fehlern. Es ist einfach nicht einfach, den Überblick über alle sich ändernden Werte zu behalten. Hier haben wir einige Beispiele dafür gesehen, wie man die offensichtliche Notwendigkeit, Zustände zu verändern, umgehen kann. Es ist etwas gewöhnungsbedürftig, aber mit ein bisschen Übung wird die Anwendung dieser Techniken vielleicht sogar ganz natürlich erscheinen.

Referenztransparenz

Die nächste wichtige Komponente von FP ist die referenzielle Transparenz. Wir sagen, dass ein Ausdruck referenziell transparent ist, wenn wir ihn überall im Code durch seinen Wert ersetzen können. Wenn du zum ersten Mal davon hörst, denkst du vielleicht, dass du das immer tun kannst. Betrachten wir ein einfaches Beispiel für eine nicht referenziell transparente Funktion.

Java

today()

Wenn ich diese Funktion aufrufe, den 29. Mai 2021 erhalte, ihren Körper durch diesen Wert ersetze und sie dann morgen aufrufe, erhalte ich die falsche Antwort. Die Heute-Funktion ist also nicht referenziell transparent.

Hier sind ein paar weitere Beispiele für nicht-referentielle Transparenz:

  • Eine Funktion, die eine Zufallszahl zurückgibt. Natürlich kannst du den Körper der Funktion nicht durch einen Wert ersetzen, den du erhältst, wenn du sie einmal aufrufst.

  • Eine Funktion, die eine Ausnahme auslöst. Ausnahmen werden in der Regel in FP vermieden. Ich werde später darauf zurückkommen.

Wenn wir alle nicht-referenziell transparenten Funktionen abschaffen (und das ist unser Ziel), verlieren wir wahrscheinlich einige wertvolle Fähigkeiten - wir können dann vielleicht bestimmte nützliche Dinge nicht mehr ausdrücken. Sei versichert, dass es funktionale Wege gibt, diese Dinge auszudrücken.

Ein verwandtes Konzept, das du in Schriften über FP finden wirst, ist Reinheit. Leider gibt es in der Literatur einige Verwirrung über die Beziehung zwischen Reinheit und referenzieller Transparenz und nicht jeder ist sich über die Bedeutung dieser Begriffe einig. Im Allgemeinen wird eine Funktion als rein bezeichnet, wenn sie keine Seiteneffekte hat und für eine bestimmte Eingabe immer dieselbe Ausgabe liefert. Das heißt, wenn die Eingabe x und die Ausgabe y ist, wird die Funktion, egal wie oft du sie mit x als Eingabeparameter aufrufst, y zurückgeben. Ein Nebeneffekt ist alles, was außerhalb des Kontexts der Funktion geschieht. Das Schreiben in eine Datei und das Auslösen einer Ausnahme sind zwei Beispiele für Seiteneffekte. Vergiss für den Moment, dass wir in Dateien schreiben müssen (obwohl wir wohl keine Ausnahmen auslösen müssen), und denke daran, wie schön es wäre, wenn wir jedes Mal, wenn wir eine Funktion mit denselben Eingabeparametern aufrufen, dieselbe Ausgabe erhalten und sich außerhalb der Funktion nichts ändert. Das ist etwas, das wir in FP genießen.

Hinweis

In FP bemühen wir uns, nur reine Funktionen zu verwenden. Das heißt, Funktionen, die keine Seiteneffekte haben und die Eigenschaft haben, dass man bei gleicher Eingabe auch die gleiche Ausgabe erhält.

Weil verschiedene Menschen unterschiedliche Ansichten darüber haben und weil die Unterschiede zwischen referenzieller Transparenz und Reinheit sehr subtil sind, werde ich die beiden Begriffe als synonym behandeln.

Ich habe gesagt, dass man kein Mathematiker sein muss, um funktionale Programme zu schreiben, und das muss man auch nicht. Aber FP kommt aus der Mathematik. Genau genommen kommt es aus zwei Bereichen: Lambda-Kalkül und Kategorientheorie. Die Kategorientheorie hat viel mit Funktionen zu tun. Und in der Mathematik sind die Funktionen rein. Wenn ein Programmierer einen Ausdruck wie x = x + 1 sieht, sagt er: "Ah, die Variable wird erhöht." Wenn ein Mathematiker sichx = x + 1 ansieht, sagt er: "Nein, das tut es nicht.2

Wie würde nun eine unreine Funktion aussehen?

Scala

object Main extends App {
 def impureFunction(x: Int): Int = {
 import scala.util.Random
 return Random.nextInt(100) + x
 }
 println(impureFunction(5))
 println(impureFunction(8))
}

Die beiden Funktionsaufrufe werden sehr wahrscheinlich unterschiedliche Ausgabewerte für denselben Eingabewert liefern. Diese Funktion ist nicht rein. Wir haben gesagt, dass mathematische Funktionen rein sind. Nun, die Programmierung hat viel von diesem mathematischen Ansatz profitiert. Funktionale Programme sind sauber, rein und elegant. Der FP-Stil mag anfangs etwas gewöhnungsbedürftig sein, aber wenn wir uns in diesem Buch schrittweise durch die grundlegenden Ideen von FP bewegen, wirst du anfangen, wie ein funktionaler Programmierer zu denken. Deine Funktionen werden rein sein und dein Code wird sauber sein.

Hinweis

Der größte Vorteil beim Schreiben von funktionalen Programmen ist jedoch, dass du viel stärker davon ausgehen kannst, dass deine Programme korrekt sind.

Ich möchte hier einen wichtigen Punkt ansprechen. Wir können FP nicht mit einer Negation definieren; wir können nicht sagen, dass es dasselbe ist wie normales Programmieren, nur dass wir dieses, jenes und das andere weglassen. Der schwierige Teil, der Teil, der von den vielen Schöpfern von FP geleistet wurde, ist, wie man alles, was wir brauchen, auf funktionale Weise ausdrücken kann.

Funktionen höherer Ordnung

In FP dreht sich alles um Funktionen. Was wir in einer FP-Sprache wollen, ist die Möglichkeit, Funktionen als Bürger erster Klasse zu behandeln. Das bedeutet, dass wir in der Lage sein sollten, sie als Funktionsparameter zu übergeben und sie als Rückgabewerte zurückzugeben. Erläutern wir nun, warum Funktionen höherer Ordnung ein wichtiger Bestandteil von FP sind. Ein wichtiges Ziel von FP ist es, zum Kern der Sache vorzudringen. Das bedeutet, dass wir in der Lage sein müssen, Konzepte in unserer Sprache prägnant auszudrücken. Wenn wir zum Beispiel jede ganze Zahl in einer Liste quadrieren wollen, sollten wir die Liste nicht in einer Schleife durchgehen und jede Zahl durch Quadrieren ändern müssen. Wir sollten einfach in der Lage sein, eine square Funktion direkt auf jedes Element der Liste gleichzeitig anzuwenden. Mit der Funktion map ist das in vielen Sprachen möglich. Sie erlaubt es uns, auf einer höheren Abstraktionsebene zu arbeiten. Diese höhere Ebene entspricht einer Funktion höherer Ordnung. Wir werden dies im weiteren Verlauf von als ein wichtiges Thema sehen.

Ein imperativer Ansatz:

Python

def square(nums):
    squared = []
    for i in nums:
    	squared.append(i*i)
    return squared

Ein funktionaler Ansatz:

Python

def square(nums):
    return map(lambda n: n*n, nums)

Wie im Anhang gezeigt, ist lambda eine Möglichkeit, eine anonyme Funktion zu erstellen, d.h. eine Funktion ohne Namen, sozusagen "on the fly". Die Funktion map wirkt auf die Mitglieder einer Liste und wendet sie auf einmal auf alle Elemente der Liste an.

Faule Bewertung

Eine weitere Komponente von FP ist die faule Auswertung. Das bedeutet einfach , dass ein Ausdruck erst dann ausgewertet wird, wenn er gebraucht wird. Streng genommen ist dies nicht notwendig, damit eine Sprache funktional ist, aber oft neigen Sprachen, die von Natur aus funktional sind, dazu, faul zu sein. Haskell zum Beispiel ist standardmäßig faul und kann als archetypische FP-Sprache betrachtet werden. Sie wurde von einem Komitee aus Akademikern entwickelt und geht keine Kompromisse ein, wenn es um funktionale Prinzipien geht. Die meisten populären Sprachen sind jedoch nicht faul, sondern verwenden die so genannte eifrige Auswertung. Das bedeutet, dass ein Ausdruck jedes Mal ausgewertet wird, wenn er auftaucht. Wie du im folgenden Beispiel sehen wirst, hat die faule Auswertung zwei Vorteile:

Stell dir vor, du möchtest deine eigene if Anweisung definieren. Nennen wir die Funktion myIf. Du möchtest z.B. jeder if Anweisung eine Protokollierungszeile hinzufügen. Wenn du das Folgende versuchst, wirst du auf ein Problem stoßen.

Scala

def myIf(condition: Boolean, thenAction: Unit, elseAction: Unit): Unit =
	if (condition)
      	    thenAction
        else elseAction

Kannst du das Problem mit dieser Definition erkennen? Bei der eifrigen Auswertung, die in den meisten Sprachen üblich ist, werden beim Aufruf der Funktion zunächst alle Parameter ausgewertet. Im Fall von myIf werden also sowohl then​Action als auch elseAction ausgewertet, obwohl du willst, dass nur einer von ihnen ausgewertet wird, abhängig von der Bedingungsvariablen. Mit der "Lazy Evaluation" würde dies jedoch funktionieren. In diesem und ähnlichen Fällen könntest du deine eigenen Kontrollanweisungen schreiben.

Hinweis

Bei der eifrigen Auswertung werden die Funktionsparameter ausgewertet, sobald die Funktion aufgerufen wird. Bei der lazy evaluation werden sie erst ausgewertet, wenn sie benötigt werden.

Ein weiterer Vorteil ist die Leistungsverbesserung in bestimmten Situationen. Da der faule Code nur dann ausgewertet wird, wenn er benötigt wird, wird er oft weniger ausgewertet als im Fall der eifrigen Auswertung. Das kann das Programm beschleunigen.

In Scala können wir Call-by-Name-Parameter verwenden. Im folgenden Code werden thenAction und elseAction nur dann ausgewertet, wenn sie benötigt werden. Das heißt, sie werden faul ausgewertet. Der folgende Code wird wie erwartet funktionieren.

Scala

def myIf(condition: Boolean, thenAction: Unit, elseAction: Unit): Unit =
	if (condition)
             thenAction
        else elseAction

Mit Lazy Evaluation können wir unsere eigenen Versionen von Operatoren wie if oder while erstellen.

Zusammengefasst:

  • Lazy Evaluation ermöglicht es dir, Kontrollflussstrukturen direkt in deinem Code zu definieren, anstatt sie als Operatoren in die Sprache einzubauen.

  • Es kann die Leistung beschleunigen.

Denken wie ein Funktionsprogrammierer

In diesem Buch werden wir uns darauf konzentrieren, wie ein funktionaler Programmierer zu denken. Es gibt zwar eine Vielzahl von Ansätzen für funktionale Programmierung, aber einige Konzepte sind für alle Ansätze gleich. Funktionale Programmierer verändern zum Beispiel keine Zustände. Das heißt, wenn eine Variable einmal gesetzt wurde, wird sie nie wieder geändert. Außerdem neigen funktionale Programmierer dazu, viele Funktionen höherer Ordnung zu verwenden. Das sind Funktionen, die andere Funktionen als Parameter annehmen und/oder eine Funktion als Rückgabewert zurückgeben.

Hinweis

Um zu wissen, wie ein funktionaler Programmierer wirklich denkt, muss er eine Reihe von Idiomen oder Mustern kennen, die funktionalen Code fördern.

Es ist schön und gut, wenn ich dir sage, dass du deine Variablen nicht verändern sollst, aber wenn du nicht weißt, wie du das umgehen kannst, macht die Implementierung von Unveränderlichkeit vielleicht keinen Sinn. Mit anderen Worten: Muster sind ein wichtiger Bestandteil von FP.

Du hast vielleicht schon gehört, dass funktionale Programmierer sich nicht so sehr um Muster kümmern wie objektorientierte Programmierer. Das ist ein Irrglaube. Richtig ist, dass der Begriff Muster im Kontext von FP etwas anderes bezeichnet als die Gang of Four Muster.3 Die Gang of Four-Muster (z. B. Prototyp-, Proxy- und Flyweight-Muster) wurden im Kontext von OOP entwickelt. Sie können größtenteils in einem funktionalen Stil implementiert werden und sind nützlich für die Gestaltung von Programmen, aber es gibt nichts besonders Funktionales an dieser Art von Mustern. Man könnte sagen, sie sind funktional-neutral. Während die Gang of Four-Muster funktional-neutral sind, gibt es eine andere Kategorie von Software-Mustern, die explizit funktional sind. Diese Muster, wie z. B. die Funktor- und Monadenmuster, leiten sich von Ideen aus der Kategorientheorie ab. Wir werden sie in Kapitel 3 genauer betrachten.

Die Vorteile von FP

Die Vorteile von FP werden immer deutlicher. Es hilft uns bei unserem Streben nach fehlerfreiem Code. Oder so nah an fehlerfreiem Code, wie es möglich ist. Und wie macht es das? Indem wir unser Gehirn so umprogrammieren, dass wir die Welt nicht mehr als eine Ansammlung von Objekten sehen, von denen jedes seinen eigenen, sich ändernden Zustand und Prozesse hat, die diesen Zustand verändern. Mit einem Perspektivwechsel können wir den Zustand als Schuldigen identifizieren.

Warnung

Wenn sich der Zustand ändert, müssen wir ihn im Auge behalten, was bedeutet, dass es mehr Komplexität zu verwalten gibt und mehr Bugs. Das ist das Problem, das FP löst.

Wir Menschen können nur so viel Komplexität bewältigen, bis wir anfangen, Code zu schreiben, der nicht ganz korrekt ist. "Aber warte", sagst du, "die Welt besteht aus Objekten. Und diese Objekte haben einen Zustand, der sich mit der Zeit ändert! Es ist also richtig, die Welt auf diese Weise zu modellieren. So ist die Welt nun mal!" Das heißt aber nicht, dass wir nicht anfangen können, die Welt in funktionaleren Begriffen zu sehen (und zu modellieren). Im Moment ist es wichtig zu erkennen, dass wir nicht wirklich wissen, wieman fehlerfreie Software schreibt. Ich kannte einen Informatikprofessor, der seine Einführungsveranstaltung in die Programmierung einmal mit dem Satz begann: "Die menschliche Ethnie weiß noch nicht, wie man programmiert."

Vielleicht ein bisschen dramatisch, aber wahr. Projekte liegen in der Regel über dem Budget und dauern viel länger als vorhergesagt. Der Grund dafür ist die Komplexität. Programmieren ist die Kunst, die Wissenschaft und die Technik des Komplexitätsmanagements. FP bringt Werkzeuge mit sich, mit denen wir versuchen können, diese Komplexität einzudämmen und zu kontrollieren. Werkzeuge wie Unveränderlichkeit, referenzielle Transparenz und Funktionen höherer Ordnung, um nur einige zu nennen. Wenn du diese Werkzeuge beherrschst, wird dein Code weniger fehlerhaft sein.

FP kann die Produktivität verbessern

FP ist also ein Programmierparadigma. Welche anderen Paradigmen gibt es?

Die beliebteste ist wohl OOP.4 Wenn du zum Beispiel in Java, C#, C++ oder Python programmiert hast, bist du wahrscheinlich mit dieser Art der Programmierung vertraut. In diesem Fall wird die Welt als eine Sammlung von Objekten modelliert, die jeweils ihren eigenen Zustand und ihr eigenes Verhalten haben. OOP hat viele Vorteile, darunter Abstraktion, Kapselung und Vererbung. Aber selbst mit diesen Vorteilen leidet der Code oft darunter, dass er zu viel Zeit und Geld kostet. Bevor FP und OOP populär wurden, gab es die imperative Programmierung. Oberflächlich betrachtet ähnelt die imperative Programmierung ein wenig der FP. sind die wichtigste Softwareabstraktion, die in der imperativen Programmierung verwendet wird; es gibt keine Objekte oder Klassen. Bei näherer Betrachtung stellt man jedoch fest, dass Zustände veränderbar sind, Funktionen nicht referenziell transparent sind und imperative Sprachen nicht unbedingt Funktionen höherer Ordnung haben. C und Pascal sind zwei Beispiele für imperative Programmiersprachen.

Man könnte argumentieren, dass die besten Programmierer/innen besseren Code produzieren, egal welches Paradigma sie verwenden - das ist wahrscheinlich wahr. Die Frage ist nur: Wenn wir zwei gleich gut ausgebildete Entwickler haben, von denen einer mit einem objektorientierten Ansatz und der andere mit einem funktionalen Ansatz arbeitet, wer wird dann produktiver sein? Dank der Klarheit, der Leistungsfähigkeit und der höheren Abstraktionsebene wird der funktionale Programmierer schneller korrekten Code produzieren.5

FP macht Spaß!

Es gibt einen weiteren Grund, mit FP zu programmieren. Das ist vielleicht der wichtigste Grund überhaupt.

Hinweis

FP macht Spaß, und das aus gutem Grund.

Mit FP kommst du zum Kern der Sache. So kannst du dich auf das Wesentliche konzentrieren und mehr Zeit für die Programmierung aufwenden. Die Abstraktionsebene ist so hoch, dass es sich anfühlt, als würdest du wichtige, relevante Konzepte manipulieren, anstatt dich durch Details auf niedriger Ebene zu quälen, die genau das abbilden, was eine Maschine tut.

Die Geschichte der Programmiersprachen ist aus einer Perspektive vor allem eine Geschichte der immer höheren Abstraktionsebene. Je höher die Ebene, desto einfacher ist es, die Bearbeitung von Unmengen von Details zu vermeiden. FP zeigt uns, dass Abstraktion nicht schwierig sein muss. Ja, es gibt eine Lernkurve, aber es ist möglich, diese Kurve zu verkürzen, indem man die Umstellung Schritt für Schritt vornimmt. Wenn du zum Beispiel in Java, JavaScript oder Python programmierst, kannst du nach und nach Idiome, Strukturen und Praktiken einbauen, die deinen Code funktionaler machen, und ehe du dich versiehst, wirst du dich ganz natürlich auf funktionale Idiome verlassen, weil sie leistungsfähig sind und zur Vereinfachung beitragen. Wenn du dieses Buch aufmerksam liest, die Beispiele studierst und anfängst, einige funktionale Ideen in deinen Code einzubauen, wirst du bald große Vorteile erkennen.

Hinweis

Vielleicht möchtest du dich sogar mit Programmiersprachen beschäftigen, die das funktionale Paradigma stärker unterstützen. Sprachen wie Scala, Clojure, F# und Haskell, um nur einige zu nennen.

Scala

Ein Hinweis zu den Beispielen in diesem Buch. Ich werde Beispiele in verschiedenen Sprachen geben. Damit will ich zeigen, wie die verschiedenen Sprachen funktionale Ideen umsetzen. Es ist zwar möglich, funktionalen Code in verschiedenen Sprachen zu schreiben (je nach Sprache mit unterschiedlichem Schwierigkeitsgrad), aber einige Sprachen machen es viel einfacher und sind deshalb im Allgemeinen leistungsfähiger. Eine solche Sprache ist Scala.

Es gibt zwei Dinge, die ich über Scala sagen möchte:

  • Scala ist eine prägnante Sprache

  • Scala ist förderlich für das Schreiben von funktionalem Code

Wir werden zwar Beispiele in Java, Python und C# einbauen, aber die meisten Beispiele werden in Scala sein. Eine kurze Einführung in Scala findest du im Anhang. Besonders für die Beschreibung der Kategorientheorie ist Scala eine gute Wahl. Ich bin der Meinung, dass eine Sprache wie Scala prägnantere funktionale Konstrukte ermöglicht und dass es in vielen Fällen den Sinn der Sache verdeutlicht, wenn man zeigt, wie man es in Scala macht. Es gibt andere Sprachen, die ich für diesen Zweck hätte verwenden können - andere Sprachen, die in gewissem Maße funktional sind, wie z. B.Clojure, Meta Language (ML) undHaskell (das sehr funktional ist). Ich fand einfach, dass die Klarheit von Scala und die Leichtigkeit, mit der man funktionalen Code schreiben kann, es zu einer guten Wahl für viele der Beispiele in diesem Buch machten.

Hinweis

Wenn du bisher Java benutzt hast und dich für FP interessierst, solltest du Scala ausprobieren. Ich würde behaupten, dass du es vor allem bei neuen Projekten nützlich und produktiv finden könntest und dass es Spaß macht.

Ich habe erwähnt, dass dieses Buch Beispiele in Scala, Java, Python und C# enthalten wird. Aber je tiefer wir in die Funktionskonzepte einsteigen, desto nützlicher wird es sein, unsere Beispiele in Scala zu erstellen. Vor allem, wenn wir einige der abstrakten Konzepte von FP kennenlernen. Es gibt verschiedene Grade von FP. Einige Sprachen sind teilweise funktional und andere sind vollständig oder rein funktional, wie wir sagen.

In Tabelle 1-1 siehst du eine Tabelle, in der die Sprachen verglichen werden, die in den Beispielen in diesem Buch verwendet werden. Ich habe auch Haskell mit aufgenommen, obwohl ich keine Haskell-Beispiele verwende, weil es ein Beispiel für eine rein funktionale Programmiersprache ist und sich gut für einen Vergleich eignet.

Tabelle 1-1. Unterstützung für funktionale versus objektorientierte Programmierung in verschiedenen Sprachen
Scala Java Python C# Haskella

Unterstützt funktional

YES

TEILWEISE

TEILWEISE

TEILWEISE

YES

Unterstützt rein funktionale

NO

NO

NO

NO

YES

Unterstützt OOP

YES

YES

YES

YES

TEILWEISE

a Haskell wurde als rein funktionale Sprache entwickelt. Wenn du zum Beispiel den Wert einer Variablen einmal festgelegt hast, kannst du ihn nicht mehr ändern. Haskell hat viele andere funktionale Eigenschaften, von denen viele aus der Kategorientheorie stammen. Wir werden die Kategorientheorie in Kapitel 3 kennenlernen.

Fazit

In diesem Kapitel haben wir einen groben Überblick über die wichtigsten Merkmale einer FP-Sprache gegeben. Im weiteren Verlauf des Buches werden wir uns mit diesen Merkmalen näher beschäftigen und herausfinden, wie sich die grundlegenden Muster von FP in ihnen widerspiegeln.

1 In FP sollten alle Funktionen einen Wert zurückgeben. void ist ein sicheres Zeichen für Seiteneffekte.

2 Das soll ein Scherz sein, aber ich habe diese Reaktionen am eigenen Leib erfahren.

3 Mehr über die Gang of Four findest du in Design Patterns von Erich Gamma et al. (Addison-Wesley).

4 Obwohl es derzeit mehr OOP-Code gibt, gibt es bei vielen Entwicklern eine klare Tendenz in Richtung FP. Es bleibt abzuwarten, wie sich das entwickeln wird. Vielleicht wird ein hybrider Ansatz, der beide Ansätze mischt, die Norm werden. Vielleicht wird FP aber auch nur immer beliebter.

5 Um es klar zu sagen: Das ist meine Meinung.

Get Funktionale Programmierung lernen now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.