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
)
;
System
.
out
.
println
(
changedPerson
)
;
}
}
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
)
val
person
=
Person
(
"Katherine"
,
25
)
val
changedPerson
=
person
.
copy
(
age
=
26
)
Erstelle eine Instanz der Klasse.
Diese Zeile erzeugt eine neue Instanz von
Person
und initialisiert age auf26
. 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 thenAction
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:
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.
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.