Buchclub: Funktionale Programmierung in Java von Herbert Prähofer – IT-Berufe-Podcast #158

Um den Buchclub zu Funktionale Programmierung in Java geht es in der einhundertachtundfünfzigsten Episode des IT-Berufe-Podcasts.

TL;DR

Ich empfehle dir "Funktionale Programmierung in Java" von Herbert Prähofer als Lektüre ab dem zweiten Ausbildungsjahr. Das Buch vermittelt funktionale Grundlagen mit ausführlichen Codebeispielen und behandelt auch asynchronen Code sowie eigenschaftsbasierte Tests. Für deine Praxis und mögliche Prüfungsfragen sind besonders reine Funktionen, Funktionen höherer Ordnung und unveränderbare Daten interessant. Mit map und flatMap kannst du Daten verarbeiten und Funktionsaufrufe verketten, während CompletableFuture dir hilft, mit später verfügbaren Ergebnissen zu arbeiten.

Kapitelmarken

  • [00:00:00] Buchclub: Funktionale Programmierung in Java
  • [00:01:53] Funktionale Konzepte für IT-Azubis und Prüfungen
  • [00:05:09] Stärken des Buchs und praxisnahe Beispiele
  • [00:08:38] Java-Schwerpunkt und anspruchsvolle Beispiele
  • [00:11:41] Buchkapitel: Einstieg und sprachliche Grundlagen
  • [00:13:08] Seiteneffekte und Funktionsparameter
  • [00:14:28] Funktionen kombinieren und funktionale Datenstrukturen
  • [00:15:47] Streams und parallele Verarbeitung
  • [00:17:11] Asynchronität und Reactive Streams im Buch
  • [00:18:30] Eigenschaftsbasierte Tests und Shrinking
  • [00:20:54] Weiterführende Konzepte und Leseempfehlung
  • [00:22:04] Funktionale Grundlagen für mögliche Prüfungsfragen
  • [00:23:06] Lambda-Ausdrücke als anonyme Funktionen
  • [00:25:00] Typ-Inferenz und statische Typisierung
  • [00:27:41] Algebraische Datentypen: Produkt- und Summentypen
  • [00:32:13] Pattern-Matching
  • [00:34:47] Generics und parametrisierbare Klassen
  • [00:37:34] Ko- und Kontravarianz
  • [00:42:15] Begrenzte Wildcards in Java
  • [00:43:25] Grundprinzipien der funktionalen Programmierung
  • [00:44:01] Funktionen als First Class Citizens
  • [00:45:22] Reine Funktionen und Seiteneffekte
  • [00:47:52] Funktionen höherer Ordnung
  • [00:50:43] Bedarfsauswertung und unendliche Folgen
  • [00:53:18] Unveränderbare Datenstrukturen
  • [00:55:40] Rekursion und Tail Call Optimization
  • [00:58:21] Referenzielle Transparenz
  • [00:59:59] Funktoren und map
  • [01:01:24] Monoide, fold und neutrale Elemente
  • [01:04:52] Monaden und flatMap
  • [01:08:46] Funktionsketten und Streams im Alltag
  • [01:11:18] Asynchrone Abläufe und reagierende Anwendungen
  • [01:13:09] CompletableFuture und später verfügbare Ergebnisse
  • [01:14:32] Optional und Future über Funktionen verarbeiten
  • [01:16:48] Rückblick und Einordnung für die Prüfung
  • [01:19:16] Ergänzende Links und Feedback

Probeabo bei Audible (Affiliate)

Inhalt

Die folgenden Inhalte werden im Buch besprochen. Meiner Meinung nach sollte jeder Azubi zum Fachinformatiker Anwendungsentwicklung diese Begriffe wenigstens einmal gehört haben, besser noch sie mit eigenen Worten erklären können. Die funktionale Programmierung ist neben der Objektorientierung heutzutage sicherlich eine gängige Vorgehensweise.

Für die anderen IT-Berufe sind viele Inhalte sicherlich etwas zu speziell. Allerdings sollte jeder ITler zumindest die verbreiteten Programmierparadigmen auseinanderhalten können. Und dazu zählen meiner Meinung nach auch einige wichtige Schlagworte. So wie bei der Objektorientierung Vererbung und Polymorphie erklärt werden müssen, sollte man bei der funktionalen Programmierung Funktionen höherer Ordnung oder reine Funktionen erläutern können.

Grundlagen

  • Lambda-Ausdrücke: Anonyme Funktionen in einer kurzen Schreibweise, meist mit einem „Pfeil“ als Trenner zwischen Methodenkopf und -rumpf (z.B. ->).
  • Typinferenz: Implizite Ableitung der Datentypen z.B. von Funktionsparametern aus dem Kontext, z.B. var text = "Text" (text ist „automatisch“ ein String).
  • Algebraische Datentypen
    • Produkttypen: Der Datentyp wird definiert durch die Kombination verschiedener Werte. Das sind letztlich unsere bekannten Objekte bzw. Structs.
    • Summentypen: Der Datentyp wird definiert als Liste möglicher Alternativen bzw. Varianten, z.B. ist eine Krankenversicherung entweder eine GesetzlicheKrankenversicherung oder eine PrivateKrankenversicherung. Hierbei müssen die Alternativen nicht in einer Vererbungsbeziehung stehen, wie man am Beispiel vermuten könnte.
  • Pattern Matching: Kontrollstruktur, die Datentypen „zerlegen“ kann. Quasi switch „on steroids“.
  • Generics: Datentypen, die mit anderen Datentypen parametriert werden können. Sie bieten allgemeingültige Algorithmen an, die auf mehreren Datentypen ausgeführt werden können.
  • Ko- und Kontravarianz: Legen fest, welche Verallgemeinerungen bzw. Spezialisierungen bei Typparametern von generischen Klassen erlaubt sind. Beispiele:
    • Kovarianz: List<Person> students = new List<Student>();
    • Kontravarianz: List<Student> students = new List<Person>();

Prinzipien der funktionalen Programmierung

  • Funktionsobjekte (first class citizens): Funktionen sind fester Bestandteil der Sprache und können z.B. Variablen zugewiesen oder als Parameter übergeben werden.
  • Reine Funktionen (pure functions): Funktionen haben keine Seiteneffekte. Sie liefern zum gleichen Input immer den gleichen Output.
  • Funktionen höherer Ordnung (higher order functions): Funktionen, die andere Funktionen zurückgeben oder als Parameter bekommen.
  • Bedarfsauswertung (lazy evaluation): Ein Ausdruck wird erst dann ausgewertet, wenn sein Wert auch wirklich benötigt wird.
  • Unveränderbarkeit (immutability): Bestehende Werte sollen nicht mehr verändert werden. Stattdessen werden immer neue Werte erzeugt.
  • Rekursion (recursion): Das Mittel der Wahl zur Iteration ohne veränderliche Variablen.
  • Referentielle Transparenz (referential transparency): Ein Ausdruck kann jederzeit durch seinen Wert ersetzt werden und umgekehrt.

Funktionale Datenstrukturen

  • Funktoren: Bieten die Funktion map() an, um den enthaltenen Datentypen in einen anderen zu transformieren. Beispiel: Optional<T>.
  • Monoide: Mit Monoiden kann man eine Menge von Elementen zu einem Gesamtergebnis verknüpfen, es also reduzieren (fold). Dafür wird ein neutrales Element und eine Reduktionsfunktion benötigt. Beispiel: Addition ganzer Zahlen mit neutralem Element 0.
  • Monaden: Bieten die Funktionen of() und flatMap() an, um den enthaltenen Datentypen in einen anderen zu transformieren, wobei das Ergebnis dieser Transformation selbst wieder eine Monade ist. Beispiel: Stream<T>.
  • Listenverarbeitung mit Streams
    • Grundlagen von map(), filter() und reduce.
    • Parallele Streams und Eignung von Aufgaben für die Parallelisierung

Sonstiges

  • Asynchronität mit CompletableFutures und Reactive Streams
  • Property Based Testing mit Quickcheck
    • Ein komplett anderer Ansatz, um Funktionen zu testen. Statt Input und erwarteten Output selbst zu definieren, werden zufällige Input-Werte generiert und allgemeine Eigenschaften des Ergebnisses geprüft, z.B. dass bei einer Konkatenation zweiter Strings die Länge des Ergebnisses immer größer oder gleich der Länge des ersten Strings sein muss.

Was mir am Buch sehr gut gefällt

  • Es werden nicht nur die Grundlagen der funktionalen Programmierung erläutert, sondern auch viele andere wichtige Begriffe rund um die moderne Programmierung.
  • Gerade die Grundlagen werden sehr verständlich erklärt und mit vielen praxisnahen Beispielen angereichert.
  • Es werden auch sehr moderne Konzepte wie wie Reactive Streams und Property Based Testing erläutert.
  • Einige Grundlagen, die jeder Informatiker einmal gehört haben sollte, werden ebenfalls behandelt, z.B. Algorithmen zur Tiefensuche.
  • Auch einige Entwurfsmuster werden im Detail erklärt und mit der funktionalen Lösung verglichen.
  • Die technischen Hintergründe der Implementierung in Java werden gut verständlich erläutert. Das sollte heutzutage jeder Java-Entwickler kennen!

Was mir am Buch nicht ganz so gut gefällt

  • Die Inhalte haben natürlich einen starken Fokus auf die Implementierung in Java. Man kann zwar auch als Nicht-Java-Entwickler im Bereich der Grundlagen einiges mitnehmen, aber am besten geeignet ist das Buch wohl für Hardcore-Java-Entwickler.
  • Man merkt, dass der Autor Hochschuldozent ist. Die Beschreibungen und Beispiele sind teilweise etwas akademisch. Auch wenn die funktionale Programmierung ihren Ursprung in der Mathematik hat, ist das vielleicht für Azubis manchmal etwas schwere Kost.
  • Die verwendeten Beispiele sind teilweise sehr umfangreich. Sehr lange Codeausschnitte, die schwer nachzuvollziehen sind, hätten vielleicht gekürzt werden können.

Fazit

  • Ich kann das Buch uneingeschränkt weiterempfehlen. Es vermittelt einen sehr guten Überblick über moderne Programmmiertechniken und die funktionale Programmierung.
  • Auch Azubis werden es gut verstehen können. Ich empfehle die Literatur allerdings erst ab dem zweiten Lehrjahr, wenn schon einige Programmierprojekte umgesetzt wurden.
  • Auch als Nachschlagewerk für zentrale Begriff der funktionalen Programmierung finde ich das Buch sehr gut, da die Begriffe kurz und prägnant definiert und mit Beispielen erklärt werden.
  • Insbesondere für Java-Entwickler empfehle ich das Buch zum Verständnis der technischen Implementierung der funktionalen Programmierung in Java.

Literaturempfehlungen

Naja, was soll ich wohl anderes empfehlen als das besprochene Buch Funktionale Programmierung in Java*? 🙂

Funktionale Programmierung in Java: Eine umfassende Einführung von Herbert Prähofer (Affiliate)*

Links

Zusammenfassung

Das Buch für deine Ausbildung

Mit meinen IT-Azubis lese ich regelmäßig Fachbücher. "Funktionale Programmierung in Java" von Herbert Prähofer, Professor an der Universität Linz, empfehle ich dir als deutschsprachigen Einstieg in funktionale Konzepte und ihre Umsetzung in Java. Meine Besprechung stammt aus dem Jahr 2020.

Durch die ausführlichen Codebeispiele kannst du nachvollziehen, wie die Konzepte im Alltag funktionieren. Das Buch erläutert auch technische Hintergründe und Entscheidungen zur Sprache Java. Ergänzend behandelt es beispielsweise die Tiefensuche in Graphen oder Bäumen sowie Entwurfsmuster.

Für dich als IT-Azubi passt die Lektüre besonders ab dem zweiten Ausbildungsjahr, wenn du bereits Erfahrung beim Programmieren gesammelt hast. Im dritten Jahr kannst du die Inhalte auf dein Abschlussprojekt beziehen. Einige Beispiele sind akademisch geprägt und erstrecken sich über mehrere Seiten, wodurch du beim Lesen längere Abläufe im Kopf verfolgen musst. Den Beispielcode kannst du herunterladen und selbst ausprobieren.

Der Schwerpunkt liegt auf Java, wobei ein großer Teil der Konzepte auch auf andere Sprachen übertragbar ist. Wenn du etwa mit C# arbeitest, findest du ähnliche funktionale Möglichkeiten. Die Einführungskapitel eignen sich außerdem zum Nachschlagen einzelner Begriffe.

Funktionale Konzepte in Ausbildung und Prüfung

Seit der Neuordnung der IT-Berufe 2020 gehört das Unterscheiden von Programmiersprachen zur gemeinsamen Grundausbildung. Damit sind Programmierparadigmen für dich auch dann interessant, wenn du einen anderen IT-Beruf als Fachinformatiker:in Anwendungsentwicklung lernst. Java und C# enthalten funktionale Konzepte, die sich mit objektorientiertem Code kombinieren lassen.

Als Prüfer frage ich bei einem Java-Abschlussprojekt nach, wie eingesetzte Streams oder Optional funktionieren und weshalb du sie auf eine bestimmte Weise verwendest. Von dir als Fachinformatiker:in Anwendungsentwicklung erwarte ich, dass du grundlegende funktionale Begriffe wie reine Funktionen und Funktionen höherer Ordnung erklären kannst. Auch Generics und Entwurfsmuster gehören zu den angesprochenen Prüfungsthemen.

Funktoren, Monoide und Monaden dienen vor allem dazu, die Hintergründe besser zu verstehen. Ihre ausführliche mathematische Behandlung geht für eine mündliche Prüfung aus meiner Sicht weit.

Was die zwölf Buchkapitel behandeln

  1. Einleitung: Grundbegriffe der funktionalen Programmierung und erste Hinweise zur Umsetzung in Java.
  2. Sprachliche Grundlagen: Generics einschließlich Ko- und Kontravarianz, Typ-Inferenz sowie Typ-Constraints. Dazu kommen Neuerungen aus Java 8, etwa Default-Methoden in Interfaces, und die Grundlagen von Lambda-Ausdrücken, Closures sowie funktionalen Interfaces.
  3. Programmieren ohne Seiteneffekte: Reine Funktionen und referenzielle Transparenz. Das Kapitel behandelt außerdem Exceptions und funktionale Datenstrukturen, darunter Listen und Tupel.
  4. Programmieren mit Funktionsparametern: Funktionen höherer Ordnung anhand konkreter Beispiele, etwa der Tiefensuche. Auch die Entwurfsmuster Strategie, Kommando und Besucher werden mit funktionalen Ansätzen behandelt. Weitere Themen sind Pattern-Matching und Bedarfsauswertung.
  5. Funktionen kombinieren: Fluent Interfaces und Aufrufketten mit Java-Beispielen rund um Comparator und Collector.
  6. Funktoren, Monoide und Monaden: Die Strukturen werden über ihre Methoden und anhand von Beispielen erklärt. Optional und Streams zeigen, wie du damit in Java arbeitest.
  7. Streams: Elemente mit map umwandeln, mit filter auswählen und mit reduce zu einem Ergebnis zusammenführen.
  8. Parallele Streams: Voraussetzungen für parallel bearbeitbare Probleme und der Aufwand, den paralleler Code zusätzlich verursacht. Tests im Anhang untersuchen, wie sich das auf die Laufzeit auswirkt.
  9. Asynchronität: Mit CompletableFuture Ergebnisse verarbeiten, die erst später verfügbar sind.
  10. Reactive Streams: Asynchrone Ergebnisse und Ereignisse mit RxJava verarbeiten. Das Kapitel beschreibt die beteiligten Klassen sowie passende Einsatzfälle.
  11. Testen: Möglichkeiten von JUnit 5 und AssertJ sowie eigenschaftsbasierte Tests nach dem QuickCheck-Verfahren.
  12. Weiterführende Konzepte: Ergänzende Möglichkeiten aus anderen Sprachen, deren Übernahme in Java zum Zeitpunkt der Besprechung teilweise noch diskutiert wurde.

Sprachgrundlagen verständlich erklärt

Lambda-Ausdrücke und Typ-Inferenz

Wenn du eine kurze Funktion direkt an der Stelle brauchst, an der du sie übergibst, kannst du einen Lambda-Ausdruck schreiben. Dabei handelt es sich um eine anonyme Funktion. Ein Beispiel ist (a, b) -> a + b: Die Funktion bekommt zwei Werte und liefert ihre Summe. Je nach Syntax lassen sich Angaben zum Typ oder ein ausdrücklich geschriebenes return einsparen.

Damit der Compiler die Typen trotzdem kennt, leitet er sie aus dem Kontext ab. Das heißt Typ-Inferenz. Bei einer lokalen Variablen wie var text = "Hallo" ergibt sich der Typ aus dem zugewiesenen Wert. Die Variable bleibt statisch typisiert und hat damit einen festgelegten Datentyp.

Algebraische Datentypen

Beim Modellieren eigener Daten kannst du verschiedene Typen kombinieren oder mögliche Varianten beschreiben:

  • Ein Produkttyp verbindet mehrere Bestandteile. Ein Auto mit einer Marke als String und einem Kilometerstand als Integer ist ein solches Beispiel. Vergleichbare Strukturen kennst du als Objekte, Structs oder Records.
  • Ein Summentyp beschreibt alternative Varianten eines Datentyps. Eine Krankenversicherung kann beispielsweise als gesetzliche oder private Krankenversicherung auftreten. Die beteiligten Klassen können dabei unabhängig voneinander definiert sein.

Im Buch dienen auch boolesche Ausdrücke als Beispiel: Sowohl das Literal true als auch ein Vergleich wie a > 5 können als Varianten eines gemeinsamen Ausdruckstyps betrachtet werden.

Pattern-Matching

Wenn du mit verschiedenen Varianten eines Datentyps arbeitest, kannst du über Pattern-Matching den passenden Code auswählen. Dabei lassen sich auch Werte innerhalb eines Objekts prüfen.

Beim Auto könnte etwa dessen Höhe bestimmen, welcher Fall ausgeführt wird. Pattern-Matching kann das Objekt in seine Bestandteile zerlegen und einzelne Werte an Variablen binden, mit denen du anschließend weiterarbeitest.

Generics und Varianz

Eine Liste soll unabhängig davon funktionieren, welchen Elementtyp du darin speicherst. Über Generics definierst du die Klasse einmal mit einem Platzhalter wie T. Bei einer konkreten Liste legst du den Typ fest, beispielsweise durch List<Integer>. Der Compiler prüft dann die Typen der eingefügten und zurückgegebenen Elemente.

Mit Ko- und Kontravarianz beschreibst du, wie sich Beziehungen zwischen den Elementtypen auf die parametrisierten Typen übertragen:

  • Kovarianz folgt der Richtung der Typbeziehung.
  • Kontravarianz kehrt diese Richtung um.

In Java sind List<Student> und List<Person> zunächst eigenständige Typen, auch wenn Student von Person erbt. Würdest du eine Liste von Studierenden als beliebig beschreibbare Personenliste behandeln, könntest du darüber beispielsweise eine:n Professor:in hinzufügen. Die Liste muss jedoch ihren festgelegten Elementtyp behalten.

Java bietet dafür begrenzte Wildcards. List<? extends Person> beschreibt eine Liste mit Person oder einem davon abgeleiteten Typ als Elementtyp. Bei ? super Person bezieht sich die Grenze auf Person und übergeordnete Typen.

Grundprinzipien der funktionalen Programmierung

Funktionen als Werte

Mit Functions as First Class Citizens sind Funktionen ein Bestandteil der Sprache, mit dem du wie mit anderen Werten arbeiten kannst. Du kannst sie einer Variablen zuordnen oder als Parameter übergeben. In Java ermöglichen Lambda-Ausdrücke und funktionale Interfaces solche Arbeitsweisen.

Reine Funktionen

Wenn du einer reinen Funktion dieselben Parameter übergibst, erhältst du stets dasselbe Ergebnis. Sie arbeitet ohne Seiteneffekte. Bei einer Addition von 3 und 5 kommt dadurch jedes Mal 8 heraus.

Ein interner Zähler, der das Ergebnis beeinflusst und sich bei jedem Aufruf verändert, macht das Verhalten vom Zustand abhängig. Auch eine Ausgabe auf die Konsole ist ein Seiteneffekt. Reine Funktionen lassen sich leichter testen, weil du ihre Eingaben vollständig festlegen kannst.

Funktionen höherer Ordnung

Eine Funktion höherer Ordnung bekommt eine andere Funktion als Parameter oder gibt eine Funktion zurück. Bei map übergibst du beispielsweise eine Funktion, die aus einer Person deren Vornamen ermittelt. Für jedes Element wird diese Funktion angewendet, sodass du anschließend die Namen erhältst.

Den Ablauf zum Verarbeiten der Elemente kannst du dabei wiederverwenden. Welche Werte entstehen, bestimmst du über die übergebene Funktion.

Bedarfsauswertung

Bei Lazy Evaluation wird ein Ausdruck erst ausgewertet, sobald sein Wert gebraucht wird. Das spart Arbeit bei Ergebnissen, auf die dein Programm später gar nicht zugreift.

Damit lassen sich auch unendliche Folgen beschreiben. Ein Stream erzeugt jeweils die angeforderten Elemente. Wenn du genug Werte erhalten hast, endet die Verarbeitung an dieser Stelle.

Unveränderbare Daten

Bei Immutability behalten vorhandene Werte ihren Inhalt. Wenn du einer unveränderbaren Liste ein Element hinzufügst, entsteht eine neue Liste, während die bisherige erhalten bleibt.

Das erleichtert den Umgang mit parallelem Code, weil Threads auf Daten arbeiten können, deren Inhalt stabil bleibt. Funktionale Datenstrukturen können Teile gemeinsam verwenden, wodurch für eine neue Liste weniger kopiert werden muss. Für Java gibt es Bibliotheken mit solchen Datenstrukturen.

Rekursion

Wenn eine Funktion sich selbst aufruft, arbeitest du rekursiv. Dabei erhält jeder Aufruf seine eigenen Parameter. In funktionalen Ansätzen ist das ein Mittel, Abläufe zu wiederholen und mit unveränderbaren Werten zu arbeiten.

Mit Tail Call Optimization kann eine Sprache einen rekursiven Aufruf am Ende einer Funktion in einen iterativen Ablauf überführen. Java führt diese Optimierung bei solchen Aufrufen nicht automatisch durch. Bei tiefen Rekursionen kann deshalb der Stack voll werden.

Referenzielle Transparenz

Wenn du einen Ausdruck durch seinen Wert ersetzen kannst und das Verhalten des Programms gleich bleibt, ist er referenziell transparent. Bei einer reinen Additionsfunktion sind addiere(3, 5) und 8 gleichwertig. Dadurch kannst du leichter nachvollziehen, welches Ergebnis ein Ausdruck liefert.

Funktionale Datenstrukturen im Alltag

Funktoren und map

Über map kannst du enthaltene Werte umwandeln. Eine Struktur mit dieser Möglichkeit wird hier als Funktor beschrieben. Bei einem Stream aus Personen kann daraus ein Stream mit ihren Vornamen werden.

Auch Optional bietet map an. Enthält es einen Wert, wird die Funktion darauf angewendet. Ein leeres Optional bleibt leer.

Monoide und fold

Beim Zusammenführen mehrerer Werte zu einem Gesamtergebnis spielen Monoide eine Rolle. Mit fold verknüpfst du den bisherigen Zwischenwert jeweils mit dem nächsten Element.

Dazu brauchst du einen passenden Startwert, das neutrale Element der Operation. Für eine Summe ist das 0; bei einer Multiplikation ist es 1. So verändert der Startwert das Ergebnis nicht. Für eine Summe beginnst du beispielsweise mit 0 und addierst anschließend jeden weiteren Wert.

Monaden und flatMap

Bei Monaden stehen zwei Möglichkeiten im Mittelpunkt: Du kannst einen Wert in die Struktur einbetten und anschließend mit flatMap weiterarbeiten. Beispielsweise macht Optional.of aus einem String ein Optional<String>.

An flatMap übergibst du eine Funktion, die selbst wieder eine entsprechende Struktur liefert. Damit lässt sich eine zusätzliche Verschachtelung vermeiden.

Wenn jede Person mehrere Kurse belegt, ergibt das Abbilden der Personen auf ihre Kurslisten zunächst eine Liste von Listen. Mit flatMap kannst du die enthaltenen Kurse in einer gemeinsamen Folge zusammenführen. Weitere Aufrufe lassen sich anschließen, etwa um zu jedem Kurs dessen Namen zu erhalten.

Bei solchen Abläufen übernimmt die API das Durchlaufen der Elemente. In meinen Code-Reviews mit IT-Azubis prüfe ich deshalb, welche Schleifen sich mit Stream-Operationen ausdrücken lassen.

Asynchron mit späteren Ergebnissen arbeiten

Wenn ein Download länger dauert, sollst du währenddessen weiter mit der Anwendung arbeiten können. Bei einem asynchronen Ablauf läuft die Aufgabe im Hintergrund, während der aufrufende Code weitere Arbeit erledigt. Sobald das Ergebnis verfügbar ist, kann der dazugehörige nächste Schritt ausgeführt werden.

In Java beschreibt CompletableFuture ein Ergebnis, das später verfügbar wird. Ein Download kann beispielsweise ein CompletableFuture<File> zurückgeben. Über dieses Objekt legst du fest, was mit der Datei passieren soll, sobald sie vorliegt, etwa sie auf der Festplatte zu speichern.

Wenn du ein Ergebnis über einen wartenden Zugriff abfragst, blockiert dieser Zugriff bis zum Abschluss der Aufgabe. Durch verkettete Folgeschritte kannst du den Ablauf asynchron weiterführen.

Auch bei Optional kannst du die angebotenen Funktionen direkt einsetzen. Mit map beschreibst du, wie ein vorhandener Wert weiterverarbeitet werden soll. Die Struktur übernimmt den Umgang mit dem vorhandenen oder leeren Inhalt.

Für Web-Anwendungen mit asynchronen Requests sind solche Abläufe ebenfalls relevant. Das Buch vertieft sie mit CompletableFuture und Reactive Streams auf Basis von RxJava.

Eigenschaftsbasierte Tests

Bei Property-Based-Testing erzeugt ein Framework zufällige Eingabewerte. Du beschreibst eine Eigenschaft, die das Ergebnis für gültige Eingaben erfüllen soll. Das Framework prüft diese Eigenschaft anhand der erzeugten Fälle.

Schlägt ein Test fehl, kann Shrinking die Eingabe schrittweise verkleinern, bis ein kleinerer Fall übrig bleibt, der den Fehler weiterhin auslöst. Dadurch erkennst du leichter, welche Eingabe den Fehler verursacht. Das Buch behandelt diesen Ansatz neben JUnit 5 und AssertJ.

Ergänzende Materialien

Meine Vorträge und Artikel zur funktionalen Programmierung sind in den Shownotes unter itberufepodcast.de/158 verlinkt. In Episode 106 findest du außerdem ein Interview zur funktionalen Programmierung.

Transkription der gesamten Episode

Automatisch erzeugte Transkription der Episode

[0:00] Herzlich willkommen zum IT-Berufe-Podcast, dem Podcast rund um die Ausbildung in den

[0:08] IT-Berufen. In dieser Episode gibt es einen Buchclub zum Buch Funktionale Programmierung

[0:13] in Java. Viel Spaß! Hallo und herzlich willkommen zur 158. Episode des IT-Berufe-Podcasts. Mein

[0:26] Name ist Stefan Macke und heute mache ich mal wieder eine Buchclub-Episode. Wer das nicht

[0:31] kennt, ich lese mit meinen Azubis regelmäßig Bücher rund um die Ausbildung in der IT und einige

[0:36] davon bespreche ich jetzt auch hier oder habe ich auch schon hier besprochen, zum Beispiel das ganze

[0:40] IT-Handbuch von Sascha Kersten. Das kennst du bestimmt, dein sinnvoller Ausbildungsbegleiter.

[0:44] Aber ich habe auch noch mehr Bücher, zum Beispiel JUnit mit Java 8 und verschiedene andere Sachen

[0:49] gemacht. Und jetzt habe ich ein neues Buch gefunden, was ich richtig, richtig gut finde

[0:53] als Einstieg in die Funktionale Programmierung. Und das heißt Funktionale Programmierung in Java.

[0:59] Wenig überraschend in dieser Titel. Das ganze wurde geschrieben von einem Professor an der

[1:05] Universität Linz in Österreich und der Name ist Herbert Prähofer. Ist tatsächlich auch mal ein

[1:12] original deutsches Buch, also kein englisches, was dann übersetzt wurde oder sonst irgendwas,

[1:16] sondern wirklich mal ein gutes deutsches Buch um die Programmierung, also rund um die Programmierung.

[1:20] Das ist erwähnenswert, so viele gibt es davon nicht. Und ich habe das Buch tatsächlich verschlungen,

[1:25] weil da so viele coole Sachen drin sind. Also eine ganz allgemeine Einführung in die Funktionale

[1:29] Programmierung, aber auch Spezifika, wie das ganze in Java umgesetzt ist und auch noch viele

[1:33] weitergehende Themen. Nicht nur die Funktionale Programmierung, sondern auch moderne Sachen, wie

[1:36] zum Beispiel Reactive Streams. Und von daher kann ich das Buch Stand heute 2020 auf jeden Fall

[1:41] uneingeschränkt weiterempfehlen und an den Grundlagen wird sich auch so schnell nicht viel

[1:45] ändern. Also ich denke mal auch in den nächsten Jahren kann man das noch gut als Einstieg in die

[1:48] Funktionale Programmierung lesen in der Ausbildung. So, und jetzt könnte man meinen,

[1:53] Funktionale Programmierung, das ist nur was für Anwendungsentwickler. Ja, im Detail natürlich

[1:57] schon. Im täglichen Anwendungsbereich bei der Programmierung natürlich sollte man als

[2:02] Anwendungsentwickler vielleicht sich inzwischen damit mal beschäftigt haben, weil die Objektorientierung

[2:05] ja nicht mehr nur das Einzige ist, was heute in den Sprachen angeboten wird und auch nicht das

[2:09] Einzige Sinnvolle. Es gibt viele gute Einsatzszenarien, wo ich funktionale Paradigmen

[2:13] einsetzen kann. Aber davon abgesehen ist trotzdem die Funktionale Programmierung auch für alle

[2:17] anderen IT-Berufe interessant. Denn gerade ab 2020, Novellierung der IT-Ausbildungsordnung,

[2:23] da steht auch für die anderen IT-Berufe sowas wie Unterscheiden verschiedener Programmiersprachen

[2:28] drin. Und zwar sogar in der Grundausbildung, die für alle IT-Berufe identisch ist. Und da

[2:31] sollte man ja wenigstens mal, wenn man schon nicht Programmiersprachen konkret unterscheiden kann,

[2:35] was in der Prüfung wohl nie abgefragt wird, dann eher die Programmierparadigmen abgrenzen können.

[2:40] Und Objektorientierung, das macht vermutlich jeder Azubi in der Berufsschule und vielleicht auch in

[2:45] seinem Unternehmen, weil es ja immer noch sehr dominant ist. Aber Funktionalprogrammierung gehört

[2:49] für mich heutzutage auch dazu. Unter anderem, weil viele objektorientierte Sprachen ja auch

[2:53] funktionale Aspekte inzwischen eingebaut haben. Siehe C-Sharp oder eben auch Java oder andere

[2:58] Sprachen wie Ruby. Die haben alle schon teilweise seit Jahren solche funktionalen Konzepte mit

[3:02] eingebaut, sodass man Objektorientierung und Funktionalprogrammierung ja so ein bisschen

[3:06] mischen kann und sich das Beste aus beiden Welten so ein bisschen rausholen kann. Dazu sollte ich

[3:10] aber vielleicht wissen, was ist denn eigentlich das Beste aus beiden Welten und insbesondere das

[3:14] Beste an der Funktionalprogrammierung. Und dafür empfehle ich ab jetzt dieses Buch, weil er wirklich

[3:19] sehr gut erklärt wird, auch von, also from scratch quasi, von den Grundlagen, was es damit so auf sich

[3:24] hat und was man damit schönes machen kann. Und wenn du persönlich Softwareentwickler bist und

[3:28] vielleicht nicht unbedingt gerade für die Prüfung lernst, aber immer schon mal wissen wolltest, wie

[3:32] so ein Stream in Java oder ein Optional oder sonstiges, was aus dem Bereich der Funktionalprogrammierung

[3:38] kommt, eigentlich funktioniert, dann kann ich das Buch nur wärmstens empfehlen. Denn gerade für

[3:43] Java-Entwickler ist da richtig, richtig viel drin, weil die technischen Hintergründe der Implementierung

[3:47] in Java wirklich im Detail erläutert werden, sodass man also gut nachvollziehen kann, warum auch die

[3:51] Sprachdesigner sich für bestimmte Sachen entschieden haben und das einfach auch versteht, wie der Code

[3:55] funktioniert, den man da jetzt mit diesen funktionalen Möglichkeiten da anreichert. Also

[4:01] meiner Meinung nach auch ein tolles Buch für fertige Entwickler, die sich immer schon mal mit

[4:05] den Hintergründen dessen, was sie da eigentlich den ganzen Tag machen, beschäftigen wollen. Und wenn wir

[4:09] dann noch mal zurück auf die Prüfungsinhalte kommen, also so wie ich bei der Objektorientierung

[4:13] erwarten würde, dass ein Prüfling zumindest im Bereich der Anwendungsentwicklung so etwas wie

[4:17] Vererbung, Polymorphie, Kapselung erklären kann, die klassischen drei Säulen der Objektorientierung, würde

[4:22] ich heutzutage auch erwarten, dass man das gleiche auf die funktionale Programmierung überträgt und dann so

[4:26] etwas wie Funktionen höherer Ordnung oder reine Funktionen erklären kann, weil das einfach wichtige

[4:32] Bestandteile dieses Programmierparadigmas sind. Und Spoiler, die ganzen Sachen werden im Buch rauf

[4:37] und runter erklärt und richtig schön mit anschaulichen Beispielen und nachvollziehbar, auch für Azubis,

[4:42] wie ich finde. Richtig gut erklärt, deswegen meine absolute Empfehlung für dieses Buch. Und ich würde

[4:47] jetzt diese heutige Episode zum Anlass nehmen, mal so ein bisschen über das Inhaltsverzeichnis zu

[4:51] gehen. Was bietet das Buch eigentlich konkret für Inhalte? Und dann würde ich auch so ein bisschen

[4:55] die Quintessenz des Ganzen hier mal erzählen, denn ich möchte ja auch, dass du das hier für

[4:59] die Prüfungsvorbereitung nutzen kannst. Und anhand des Buchs erkläre ich heute also mal so ein

[5:05] bisschen funktionale Programmierung und was es da so mit den wichtigsten Eigenschaften auf sich hat.

[5:09] Doch bevor wir das durchgehen, vielleicht mal einmal im Detail, warum ich das Buch jetzt wirklich

[5:13] gut finde und weiter empfehlen kann. Also es gibt einige Sachen, die ich sehr, sehr gut finde. Die

[5:17] gehen wir mal kurz durch. Gibt auch ein paar Sachen, die ich vielleicht etwas optimieren würde. Und ja,

[5:22] ich gehe mal meine Liste kurz durch. Also Nummer 1, das Buch redet zwar von funktionaler Programmierung.

[5:26] Es steht im Titel, aber es werden auch sehr, sehr viele Sachen am Rande davon erklärt, die

[5:31] einfach für den Alltag eines Entwicklers heute wichtig sind. Ich nehme jetzt nur das Beispiel

[5:35] Reactive Streams. Ja, also richtig moderne Sachen für Praxiseinsatz. Was kann ich damit machen? Was

[5:40] bringt mir das? Und so weiter. Also ich kann das im Jahr 2020 auch gerade für diesen heißen Scheiß

[5:46] auf jeden Fall empfehlen, weil es da wirklich sehr, sehr gut verständlich und mit Beispielen

[5:49] erklärt wird, wie das ganze Zeug funktioniert. Das ist auch mein weiterer nächster Punkt. Die

[5:55] Beispiele sind richtig gut. Die sind teilweise auch sehr lang und gut verständlich und ja,

[6:00] praxisnah gestaltet. Also das hat mir auch sehr gut gefallen. Nicht nur in der Theorie,

[6:04] wie funktioniert das denn, sondern mit konkreten, nachvollziehbaren Code-Beispielen alles erklärt.

[6:09] Was ich auch sehr gut fand, zusätzlich zu den Reactive Streams, war nochmal im Bereich Testen.

[6:13] Das Property-Based-Testing hat auch viel mit funktionaler Programmierung zu tun,

[6:16] wenn man das da gut einsetzen kann. Aber einfach mal eine ganz andere Art und Weise,

[6:20] wie ich meine Tests schreibe zum Beispiel. Das fand ich auch sehr interessant, dass das hier im

[6:23] Buch gleich mit abgehakt ist und mit drin ist. Und es werden aber auch nebenbei noch ganz andere

[6:28] allgemeine Sachen besprochen, die nicht zwangsläufig was mit funktionaler Programmierung

[6:32] zu tun haben, die aber jeder Informatiker meiner Meinung nach wenigstens mal gehört haben sollte.

[6:36] Ich nehme mal ein Beispiel. Ein Algorithmus zur Tiefensuche in einem Grafen oder einem Baum. Wenn

[6:42] du das noch nie gehört hast, ja, das Buch hilft dir weiter. Da wird quasi so nebenbei als Beispiel

[6:46] für die Arbeit auf funktionalen Datenstrukturen sowas wie eine Tiefensuche erklärt. Das finde

[6:51] ich richtig, richtig gut. Also es ist quasi auf jeder Seite ist eigentlich irgendwas drin,

[6:54] was man mitnehmen kann für die Praxis. In die gleiche Bresche springen dann die

[6:58] Entwurfsmuster, die auch im Buch erklärt werden. Einfach um zu zeigen, dass man in

[7:02] funktionaler Programmierung vielleicht solche Dinge gar nicht mehr braucht. Bestimmte kann

[7:06] man nämlich dort einfach durch eingebaute Funktionalität umsetzen. Und die werden hier

[7:10] auch erklärt. Und wie ich sehe, es wird in den Prüfungen zumindest für Anwendungsentwickler

[7:14] auch immer weiter in Richtung solcher Entwurfsmuster gehen. Ich habe die Handreichung

[7:19] für die Neuordnung der IT-Berufe dieses Jahr gerade noch mal durchgeguckt und da steht auch

[7:24] explizit irgendwo Design-Pattern, also Entwurfsmuster als Bestandteil der Ausbildung. Das heißt,

[7:30] das muss man heute ja zumindest auch mal gehört haben, was ist das Ganze eigentlich und vielleicht

[7:35] auch mal zwei, drei nennen können. Und die werden hier im Buch unter anderem auch mit erklärt. Also

[7:38] auch eine schöne Geschichte für die Prüfung zum Beispiel. Und dann hatte ich eben schon gesagt,

[7:43] die technischen Hintergründe der Implementierung in Java finde ich richtig klasse. Ja, ich kann

[7:49] eigentlich nicht als Entwickler sagen, so ich benutze jetzt Java 8 und mache hier mal ein

[7:51] Optional und bin ich funktionaler Programmierer, sondern ich müsste ja vielleicht auch mal

[7:54] verstehen, was dahinter liegt, wie das Ganze implementiert ist, warum das vor allem wie

[7:59] implementiert ist. Und das würde ich zum Beispiel auch in der Prüfung abfragen. Habe ich auch schon

[8:03] in mehreren mündlichen Prüfungen gemacht. Wenn also ein Entwickler mit einem Java-Projekt um die

[8:07] Ecke kommt und diese ganzen Sachen benutzt hat, Optional und Stream und irgendwas, und dann aber

[8:11] nicht richtig benutzt hat, ja, also wie es eigentlich vorgesehen war, dann frage ich da

[8:16] schon gerne mal nach. Und da offenbaren sich dann sehr schnell einfach auch Lücken von der

[8:21] Erkenntnis, wie man mit diesen Strukturen eigentlich umgehen sollte und warum vor allem.

[8:24] Und das sind so Sachen, die sind für mich in einem Java-Abschlussprojekt auf jeden Fall Teil

[8:29] einer mündlichen Prüfung. Von daher muss man das nicht nur anwenden können, sondern auch verstehen

[8:33] können. Und das wird mit diesem Buch sehr, sehr gut abgedeckt, weil alles im Detail erklärt wird.

[8:38] So, ein paar Punkte habe ich auch, die mir nicht ganz so gut gefallen. Nummer eins ist gleich das,

[8:43] was ich gerade erzählt habe. Es ist schon ein starker Fokus auf Java. Ja, also Leute,

[8:48] die es nicht täglich mit Java entwickeln können, vermutlich mit der genauen Implementierung und den

[8:52] Hintergründen in Java jetzt nicht so viel anfangen. Gefühlt sind trotzdem, ich sage einfach mal,

[8:56] 80 Prozent des Buchs unabhängig von Java, sind einfach nur die Konzepte. Aber natürlich,

[9:01] wenn man jetzt nicht mit Java arbeitet, ja, muss man halt die technischen Details vielleicht ein

[9:05] bisschen überspringen oder sie interessieren einen vielleicht nicht so viel. Na gut, aber das

[9:08] Buch heißt ja auch in Java, von daher weiß man, worauf man sich einlässt. Aber ich finde schon,

[9:13] dass die Konzepte im Großen und Ganzen allgemein erklärt werden. Und Java-Code lässt sich jetzt

[9:19] auch relativ einfach auf andere Sprachen übertragen. Ich nehme jetzt mal C-Sharp als

[9:22] Beispiel. Ist ja schon sehr ähnlich und die Konzepte sind dort auch sehr ähnlich umgesetzt.

[9:26] Also von daher ist es nicht nur was für Hardcore-Java-Entwickler, aber wenn du einer

[9:30] bist, wird das für dich auf jeden Fall noch mehr Mehrwert bringen, sage ich mal.

[9:34] Dann merkt man stellenweise schon, dass der Autor ja Hochschuldozent ist. Ja,

[9:40] also einige Beispiele sind schon etwas, ich sag mal, akademisch vorsichtig gesagt. Und auch,

[9:45] wenn die funktionale Programmierung ja eigentlich auch aus der Mathematik kommt,

[9:48] ist das vielleicht für Azubis, die gerade einsteigen, ein bisschen schwere Kost. Ja,

[9:53] also einige Beispiele sind wirklich sehr, sehr lang und auch etwas schwieriger nachzuvollziehen,

[9:59] deswegen. Von daher, wenn ich jetzt ein Azubi im ersten Layer bin, dann ist das vielleicht noch

[10:03] nicht ganz die richtige Lektüre für mich, würde ich sagen. Aber ab dem zweiten Layer und vor allem

[10:08] auch im dritten, wenn es aufs Abschlussprojekt zugeht und man die Sachen eigentlich in der

[10:11] Praxis anwenden können sollte, da ist das Buch sehr, sehr gut geeignet. Und da glaube ich auch,

[10:15] dass Azubis das verstehen können. Gerade zu Beginn der Ausbildung ist es vielleicht etwas

[10:20] schwierig zu verstehen oder etwas schwieriger. Ja, und letzter Punkt habe ich gerade schon gesagt,

[10:27] die Beispiele sind teilweise sehr umfangreich, also Codebeispiele, die über mehrere Seiten gehen. Da

[10:33] muss man dann schon, wenn man das liest, im Kopf versuchen nachzuvollziehen, was passiert jetzt wie.

[10:37] Das ist vielleicht doch etwas schwierig. Natürlich kann man sich den Code auch runterladen,

[10:41] natürlich gibt es den als Anhang zum Buch, kann man sich einfach downloaden, entpacken und los

[10:45] geht's. Aber wenn man jetzt nur das Buch liest, so wie ich es auch gemacht habe, dann ist das

[10:49] teilweise doch etwas schwierig nachzuvollziehen. So, nichtsdestotrotz, wie gesagt, uneingeschränkte

[10:55] Empfehlungen auch für die Ausbildung. Ja, man hat auch mal ein bisschen schwierigere Kosten. Wir

[10:59] sind ja auch Anwendungsentwickler und nicht irgendwie, ja, was auch immer. Das ist ja unser

[11:03] Beruf, also wir müssen uns damit eher auseinandersetzen. Und auch für alle anderen IT-Berufe,

[11:07] ich habe es gesagt, empfehle ich das. Und wenn man sich nur die Einführungskapitel durchliest,

[11:11] was ist überhaupt eine Funktion, was ist eine Funktion höherer Ordnung und so weiter, dafür

[11:15] eignet sich das Buch auch sehr, sehr gut, so als Nachschlagewerk. Noch mal eben kurz auf ein,

[11:19] zwei Seiten nachlesen, was war noch mal eine Funktion höherer Ordnung. Und das ist da wirklich

[11:23] sehr, sehr schön definiert und mit Beispielen auch hinterlegt. Und dafür finde ich es auch eine,

[11:28] ja, eine sehr gute Lektüre, auch gerade noch mal für die Prüfungsvorbereitung, wenn ich noch mal

[11:32] gezielt die Vor- und Nachteile der funktionalen Programmierung oder überhaupt die Ideen der

[11:37] funktionalen Programmierung mir anschauen möchte. Okay, dann würde ich sagen, wir gehen mal grob

[11:41] über das Inhaltsverzeichnis, was alles so im Buch zu finden ist. Es geht los mit Kapitel 1,

[11:46] Einleitung. Einleitung ist sonst ein Kapitel, was man häufig mal überspringen kann, in diesem Fall

[11:50] aber nicht, denn da sind die elementaren Konzepte und Begriffe der funktionalen Programmierung drin

[11:54] erklärt und auch schon der erste Hinweis auf FP in Java. Also da sind wirklich viele Sachen drin,

[11:58] gerade so absolute Basics, die wirklich kurz und knackig noch mal definiert werden, damit man

[12:03] direkt einsteigen kann in die späteren Kapitel. Also finde ich richtig gut, auf einem guten Niveau,

[12:08] auch für Azubis verständlich erklärt. Dann kommen wir noch mal zu sprachlichen Grundlagen. Das

[12:12] Kapitel 2, da gibt es noch mal ein sehr langes Kapitel zum Thema Java Generics. Und Generics,

[12:18] also generische Klassen, ist auch so ein klassisches Prüfungsthema, was viele Prüflinge

[12:23] nicht so auf dem Schirm haben. Und hier wird noch mal wirklich rauf und runter alles erklärt,

[12:27] inklusive eines meiner Lieblingspunkte, und zwar Co- und Kontravarianz. Wer das in der Prüfung

[12:32] erklären kann, der kriegt wahrscheinlich eine 1 mit Sternchen, wenn der Rest dann auch noch passt.

[12:36] Aber alle möglichen anderen Sachen, wie Typ-Inferenz und Typ-Constraints und so weiter,

[12:41] alles wird hier erzählt. Auch die Neuerungen in Java 8, also Default-Methoden-Interfaces zum

[12:47] Beispiel. Und was ist überhaupt ein Lambda-Ausdruck? Wofür brauche ich sowas? Was ist ein Closure? Und

[12:52] dann noch mal Funktionalinterfaces, die ja so ein bisschen die Grundlage für Lambda-Ausdrücke

[12:56] in Java sind. Das heißt, da ist richtig viel drin, gerade für Java-Entwickler. Ja, richtig

[13:03] schön zuckeriges Kapitel, sag ich mal. Ja, dann geht es weiter zum etwas allgemeineren Thema

[13:08] Programmieren ohne Seiteneffekte. Das ist Kapitel 3. Da geht es dann um die Grundlagen der

[13:12] funktionalen Programmierung, also reine Funktion, referenzielle Transparenz, Ausnahmebehandlung,

[13:19] also Exception-Handling in funktionalen Sprachen und funktionale Datenstrukturen wie Liste und

[13:24] Tuple und so weiter. Also hier geht es meiner Meinung nach sehr allgemein. Jetzt wirklich um

[13:29] die Details der funktionalen Programmierung kann ich auch sehr empfehlen. Und danach geht es weiter

[13:34] in Kapitel 4 mit Programmieren mit Funktionsparametern. Das heißt, hier geht es jetzt zum

[13:39] ersten Mal um Funktionen höherer Ordnung, also Funktionen, die andere Funktionen als Parameter

[13:43] bekommen oder zurückgeben. Was bringt mir das überhaupt? Wofür kann ich das anwenden? Und dann

[13:47] auch wirklich mit konkreten Beispielen. Ich hatte gerade schon gesagt, die Tiefensuche zum Beispiel

[13:51] oder an verschiedenen Entwurfsmustern, Strategie, Kommando und Besucher werden hier im Detail zum

[13:57] Beispiel erklärt und wie man es dann funktional ausführen würde. Und auch ganz viele coole Sachen,

[14:03] die irgendwann hoffentlich mal in Java reinkommen, wie zum Beispiel das Pattern-Matching, was hier

[14:08] auch erklärt wird und Bedarfsauswertung, das heißt ja Lazy Evaluation, dass die Datenstrukturen erst

[14:13] dann ihre Arbeit verrichten, wenn es wirklich angefordert ist. Wie man zum Beispiel auch

[14:17] unendliche Folgen mit funktionalen Datenstrukturen abbilden kann und sowas. Ja, die absoluten Basics

[14:24] und richtig gut erklärt. Ich habe schon ein paar mal gesagt. Dann geht es weiter in Kapitel 5.

[14:28] Kombination von Funktionen. Fluent Interfaces. Wie baue ich die? Funktionen, die andere Funktion

[14:33] wieder zurückgeben, die dann wieder was aufrufen und so weiter. Also Aufrufketten. Und dann aber

[14:38] auch immer schön mit Beispiel in Java, also zum Beispiel Comparator Interface oder auch das

[14:43] Collector Interface, was hier benutzt wird, um dann zum Beispiel Listen aus Streams zu machen

[14:48] und so weiter. Also richtig viele interessante, spannende Sachen drin. Und dann kommt es zum

[14:53] Hardcore, sage ich mal. Das ist das Kapitel 6. Funktoren, Monoide und Monaden. Also wenn man

[14:59] die drei in der Prüfung auseinanderhalten kann, der hat seine eins, glaube ich, sicher. Der Freie

[15:03] auch gar nichts anderes mehr. Das sind jetzt, hört sich so so hochtrabend an, die Begriffe,

[15:07] aber die sind hier richtig verständlich und gut erklärt. Also eigentlich im Prinzip anhand

[15:11] einer Klasse und welche Methoden die anbieten muss. Und dann weiß man, was ein Funktor, ein

[15:15] Monoid oder eine Monade ist. Richtig schön gemacht. Und es wird auch gezeigt, wofür man

[15:19] die braucht und was die Vorteile sind. Wie man die dann miteinander verketten kann, wenn man

[15:23] mehrere Strukturen hat, die zum Beispiel eine Monade abbilden. Richtig schön. Auch mit Beispiel

[15:28] aus Java für Monaden zum Beispiel das Optional. Das kennst du vielleicht schon. Also eine

[15:32] Datenstruktur, die kennzeichnet, dass es einen Wert gibt oder eben nicht. Das ist das Optional.

[15:37] Und eine andere Monade wäre zum Beispiel der Stream, also die Iteration über verschiedene

[15:42] Elemente. Und das wird hier gleich auch im nächsten Kapitel 7 nämlich aufgenommen,

[15:47] des Streams. Da geht es jetzt darum, wie man wirklich in der Praxis damit arbeitet. Also

[15:52] klassische Map-Filter-Reduce-Geschichten, wie wandle ich Elemente um, wie reduziere

[15:57] ich die auf einen Wert und so weiter. Das wird hier alles im Detail erklärt. Und das dürfte

[16:02] dir auf jeden Fall für deine praktische Programmierung in Java sehr viel bringen,

[16:06] falls du vielleicht noch nicht ganz so viel damit gearbeitet hast und eher so, weiß ich nicht,

[16:09] Vorschleifen oder Enhanced-For, also For-Each-Schleifen baust. Dann hilft dir das Kapitel

[16:14] sicherlich, um etwas funktionaler zu werden. Also eben mit Map-Filter-Reduce zu arbeiten,

[16:20] statt die ganze Iteration immer selbst zu programmieren. Dann geht es auch weiter im

[16:24] Anschluss. Kapitel 8 geht es um parallele Streams. Auch sehr schön, denn ein wichtiger Grund,

[16:29] warum die funktionale Programmierung heutzutage so an Fahrt aufnimmt, ist ja, weil ich die gut

[16:34] parallel ausführen kann. Vorausgesetzt, mein Problem lässt sich parallelisieren. Ja,

[16:37] das ist alles richtig. Wird hier auch nochmal erklärt, dass man darauf achten muss und was

[16:41] das überhaupt heißt, dass es parallelisierbar ist. Und dann wird aber auch am Beispiel des

[16:44] Streams erklärt, wie man das denn genau machen würde und tatsächlich auch nochmal die Trade-Offs

[16:48] gezeigt. Also ist es wirklich schneller, wenn ich mein Problem parallel bearbeite? Oder ist

[16:53] der Overhead nicht so groß, dass es sogar langsamer wird? Da gibt es auch im Anhang

[16:56] des Buches eine detaillierte Aufstellung mit großen Tests, um zu schauen, ob die

[17:02] parallele Ausführung wirklich schneller ist. Also sehr, sehr gut gemacht und sehr

[17:06] wissenschaftlich begründet und gezeigt, dass es gar nicht immer besser ist, wenn man alles

[17:11] parallel ausführt. Und dann geht es weiter zum Thema Asynchronität. Da geht es jetzt konkret

[17:17] in Java mit der CompletableFuture-Klasse darum, wie ich mit Ergebnissen umgehe, die irgendwann

[17:23] in der Zukunft mir erst bereitstehen. Deswegen heißt das Ding ja Future, also klassische

[17:27] asynchrone Programmierung. Ich drücke auf den Button zum Download von irgendeiner großen Datei

[17:31] und in der Zeit soll meine App zum Beispiel ja nicht blockieren, sondern ich will weiter arbeiten

[17:35] können. Das heißt, das Ding muss irgendwie im Hintergrund laufen und asynchron mir zurückmelden,

[17:40] wenn der Download zum Beispiel fertig ist. Und hier gibt es eine sehr schöne Einführung,

[17:43] warum ich sowas brauche, wie man sowas umsetzt und dann direkt am konkreten Beispiel der Reactive

[17:48] Streams in Kapitel 10. Da geht es dann konkret um RxJava. Das Framework hast du vielleicht schon

[17:55] mal aus einem anderen Umfeld gehört. RxJS gibt es zum Beispiel auch für JavaScript. Das ist also

[17:59] ein Prinzip, wie ich reaktiv mit solchen Streams umgehe, also mit asynchronen Ergebnissen oder

[18:04] Ereignissen und wie ich die dann ja im Prinzip mit MapFilterReduce verarbeiten kann. Und das

[18:10] ist hier sehr ausführlich erklärt, wie das funktioniert, wie die Klassen heißen, was die

[18:14] machen, wofür die da sind und auch wieder natürlich Vor- und Nachteile. Wann mache ich

[18:19] das denn überhaupt? Wo bringt mir das tatsächlich wirklich was? Und auch wieder immer schön mit

[18:24] Java-Beispielen angereichert. Also hat mir sehr gut gefallen. Dann Kapitel 11. Testen finde ich

[18:30] auch super, super wichtig. Ganz unabhängig vom Programmierparadigmen. Testen ist immer wichtig,

[18:34] aber funktionale Programme kann ich vielleicht ganz anders testen als objektorientierte. Und

[18:39] hier wird noch mal so ein bisschen zum Beispiel auf die JUnit 5 Möglichkeiten eingegangen und

[18:43] AssertJ als Matcher-Bibliothek. Und dann aber auch ein Ausblick auf das QuickCheck-Verfahren,

[18:49] also Property-Based-Testing heißt es ja, also eigenschaftsbasierte Tests, was völlig anders

[18:55] funktioniert als die klassischen JUnit-Tests, die du vielleicht kennst. Bei JUnit-Tests denkst du

[18:59] dir die Werte, die reingehen sollen, in die zu testende Methode selber aus und definierst dann

[19:03] auch selbst, was rauskommen muss. Das ist klassisch expected, das, was rauskommen muss,

[19:08] und actual, das, was die Methode wirklich zurückgibt. Und Property-Based-Testing geht einen ganz anderen

[19:14] Weg. Da werden zufällige Eingangswerte generiert und demnach kannst du auch nicht mehr sagen,

[19:18] was am Ende wirklich rauskommen muss, sondern du musst quasi eine Eigenschaft festlegen des

[19:22] Ergebnisses. Das heißt, anstatt zum Beispiel zu sagen, 3 plus 5 muss 8 ergeben, das wäre ein

[19:27] JUnit-Test mit einem konkreten Beispiel, sagst du sowas, wenn ich zwei Zahlen addiere, soll das

[19:32] Ergebnis größer sein als die eine Zahl zum Beispiel. Muss man natürlich gucken, ob das passt,

[19:37] wenn die Zahl negativ ist, was ist dann, also ist immer schwierig, aber nur mal so vom Verständnis

[19:41] her. Du sagst nicht A plus B gleich C, sondern du sagst, wenn ich zwei Zahlen addiere, dann muss

[19:45] das Ergebnis eine gewisse allgemeine Eigenschaft aufweisen und das prüft dann das Framework,

[19:50] ob das wirklich für alle möglichen Kombinationen an gültigen Zahlen zum Beispiel der Fall ist.

[19:54] Macht natürlich nicht unendlich oft für alle möglichen Kombinationen, denkt sich dann ein

[19:58] paar aus und da gibt es auch eine Möglichkeit, um zum Beispiel die Ergebnisse, wenn das Ding

[20:03] fehlschlägt, auf die kleinstmögliche Zahl zu reduzieren, für die der Test fehlschlägt.

[20:09] Ist jetzt ein bisschen schwer zu erklären in einem Podcast, aber das Buch erklärt es ja sehr gut. Das Ganze nennt sich dann Shrinking,

[20:15] also ja, verkleinern. Und das bedeutet, ich fange mit irgendwelchen Testwerten an und wenn ich sehe, das schlägt fehl, dann

[20:22] reduziere ich die Testwerte so weit, bis ich gerade noch einen Fehler produziere,

[20:26] sodass es für den Entwickler am Ende einfacher ist, herauszufinden, was der Fehler ist. Ja, wenn ich also einen Fehler habe bei

[20:31] der Zahl 2 Milliarden, dann ist das vielleicht schon ein Fehler, der ab der ersten positiven Zahl auftritt.

[20:37] Das heißt, bei 2 Milliarden wäre das geschrinkte Ergebnis dann halt die 1. Und so kann der Entwickler sehen,

[20:42] es geht um eine positive Zahl und nicht zwangsläufig um eine riesengroße Zahl zum Beispiel. Das erleichtert dann die Fehlersuche.

[20:48] Jetzt sind wir aber schon fast mitten in der Erklärung, was das ganze Zeug eigentlich bedeutet. So weit wollte ich hier noch gar nicht gehen,

[20:53] aber jetzt haben wir es abgehakt.

[20:54] Damit wären wir jetzt aber auch durch. Bleibt noch ein Kapitel übrig, weiterführende Konzepte. Das ist Kapitel 12. Da kommen noch mal so ein paar

[21:00] Ergänzungen, was man jetzt noch machen kann, wenn einem das Buch nicht ausgereicht hat zum Beispiel, ja. Und vor allem auch, was für coole Möglichkeiten

[21:07] andere Sprachen bieten, die dann hoffentlich irgendwann mal in Java eingebaut werden. Ich nehme mal nur ein Beispiel, dieses Pattern Matching. Das ist ja schon

[21:13] angedacht für Java, zumindest im Jahr 2020. Und das kommt bestimmt irgendwann. Und hier sind noch mal ein paar coole Features drin aus anderen Sprachen, die

[21:22] eventuell irgendwann so in Java landen oder vielleicht auch nicht. Und dann können wir einfach traurig sein, dass andere Sprachen so cool sind und Java nicht.

[21:28] Aber egal. Hauptsache, man hat sie mal gehört und weiß, was es damit auf sich hat.

[21:33] Gut. Also, du hörst, ich finde das Ding richtig, richtig gut. Bei jedem Kapitel kannst du wirklich irgendwas mitnehmen für deine Ausbildung oder auch wenn du

[21:40] selber Ausbilder bist unbedingt und vielleicht noch mal ein bisschen vertiefen möchtest, wie das ganze Zeug eigentlich funktioniert oder funktionieren sollte,

[21:46] kauf das Buch. Es ist wirklich richtig gut. Ich kann das absolut weiterempfehlen. Und irgendwann in der nächsten Zeit werde ich es auch mit meinen

[21:52] Azubis lesen. Das habe ich bislang noch nicht gemacht. Ich habe es selber erst gerade gelesen und gesehen, dass es richtig gut ist. Deswegen werde ich es

[21:57] jetzt mit meinen anderen Azubis lesen. Aber das muss ich natürlich auch zeitlich noch irgendwo unterbringen. Ansonsten kauft das Ding und lest es. Es ist wirklich richtig gut.

[22:04] Und jetzt würde ich zum zweiten Teil übergehen. Und zwar anhand der

[22:08] Sachen, die im Buch erklärt werden, mal so ein bisschen funktionale Programmierung noch mal durchgehen. Ich hatte dazu schon mal ein Podcast aufgenommen. Da hatte

[22:14] ich ein Interview mit Gregor Trevs. Das war die Episode 106. Da kannst du auch gerne noch mal reinhören. Da geht es auch um

[22:21] funktionale Programmierung und so ein paar allgemeine Sachen dazu. Und heute möchte ich noch mal die Sachen wirklich im Detail einzeln durchsprechen.

[22:28] Ich versuche mich auch kurz zu halten. Ich kann es jetzt nicht so machen, wie das Buch mit seinen, ich weiß nicht wie viel, 100 Seiten.

[22:34] Ich versuche kurz und knackig die Sachen zu erklären. Wenn du einzelne Sachen davon nicht verstehst,

[22:38] liest das Buch oder recherchiert im Internet oder probiert es selber aus. Die Sachen, die ich jetzt durchgehe, das sind Dinge, wo ich glaube, die könnten in

[22:45] einer Prüfung abgefragt werden. Mindestens in einer mündlichen Prüfung für Anwendungsentwickler.

[22:50] Gut, fangen wir mal vorne an. Ich habe das Ding mal Grundlagen genannt. Und da sind ja solche Sachen drin wie Lambda-Ausdrücke, Typ-Inferenz,

[22:58] algebraische Datentypen, Pattern-Matching, Generics und Co- und Kontra-Variants. Gehen wir mal der Reihe nach die Sachen durch.

[23:06] Lambda-Ausdrücke sind anonyme Funktionen.

[23:10] Punkt. Das war es eigentlich schon. Also wenn du das definieren müsstest, eine Funktion, die anonymes, was heißt das? Die hat keinen Namen.

[23:16] Daran erkennst du das. Das heißt, du hast normalerweise Methoden, die du aufrufst in deiner Objektorientierung. Die heißt dann irgendwie, keine Ahnung,

[23:22] Hupe beim Auto. Und wenn du jetzt aber eine Funktion definierst, die keinen Namen hat, dann ist sie halt anonym.

[23:28] Du kannst sie nicht beim Namen nennen, was ja quasi die Definition von anonym ist.

[23:32] Und Lambda-Ausdrücke haben jetzt den Vorteil, dass du eben nicht den ganzen Rumsch für eine normale Methode bzw. hier geht es ja um Funktionalprämierung,

[23:38] also Funktionen brauchst, sowas wie den Funktionskopf und den Rumpf und Return und ich weiß nicht was alles, sondern es ist eine kurze

[23:46] knackige Definition einer Funktion

[23:49] ohne eben diesen ganzen Overhead, den du sonst einfach hast. Das heißt, wenn du zum Beispiel die Funktion nur ein einziges Mal benutzen möchtest,

[23:55] warum musst du sie dann in eine umständliche Methode mit Klammer auf und diesem ganzen Kram packen? Brauchst du nicht.

[24:00] Und in vielen Programmiersprachen sind diese Lambda-Ausdrücke mit so einem Pfeil in der Mitte definiert. Das heißt, die Funktion ist eigentlich schon

[24:08] eine vollständige Definition, wie du sie auch kennst. Das heißt, du hast Eingangsparameter und einen Rückgabetypen. Das gibt es auch bei Lambda-Ausdrücken.

[24:15] Aber die musst du nicht explizit hinschreiben. Das heißt, du könntest eine Funktion definieren, die einfach so aussieht a, b

[24:23] Pfeil a plus b. Das wäre eine Funktion, die a und b addiert.

[24:28] Ja, das heißt, hier steht nirgendswo Function, Add, Klammer auf, Integer, sonst irgendwas steht da alles nicht, sondern wirklich nur die

[24:36] Variablen, die Parameter, die Eingangsparameter und sogar der Return-Wert, das a plus b, ist implizit. Das heißt, man muss nicht mal

[24:43] return a plus b da hinschreiben. Auch das würde diese Syntax erlauben.

[24:47] So, in Java gibt es jetzt noch Details, wann muss ich Klammern schreiben, wann nicht. Das, was ich gerade beschrieben habe, ist so die kürzeste Variante.

[24:53] Es gibt noch verschiedene Zwischenvarianten davon, aber halten wir uns damit mal nicht länger auf. Denn damit das Ganze so funktioniert, ist ein weiteres wichtiges

[25:00] Sprachfeature wichtig und das ist die Typ-Inferenz.

[25:04] Denn wenn ich jetzt einfach schreibe a, b, Pfeil a plus b, woher weiß der Compiler, welche Datentypen a und b haben?

[25:10] Das muss er wissen in statisch typisierten Sprachen wie Java. Das heißt, woher kriegt er die Information?

[25:15] Ja nun, er kann sie ableiten und zwar aus dem Kontext und das Ganze nennt man Typ-Inferenz. Das heißt, wenn du Datentypen

[25:22] implizit aus dem Kontext des Codes ableitest, beziehungsweise der Compiler das macht, dann ist das eine sogenannte Typ-Inferenz. Wie funktioniert das?

[25:30] Stell dir vor, du hast so etwas wie eine Variablen-Deklaration, der du gleich einen Wert zuweist. Also, das würdest du üblicherweise machen,

[25:38] String a gleich Text und Text in Anführungszeichen, ja. Jetzt weiß der Compiler eigentlich, wenn er sich die rechte Seite anguckt,

[25:45] Text in Anführungszeichen, huh, das ist ja ein String.

[25:48] Das heißt, es sollte die Variable auf der linken Seite jetzt eigentlich auch ein String sein und siehe da, links steht ja auch

[25:54] String a. Also, passt zusammen und funktioniert.

[25:57] Aber wenn ich das jetzt weiß, warum muss ich das dann doppelt hinschreiben? Warum muss ich String a

[26:02] hinschreiben, wenn ich aus der rechten Seite schon ableiten kann, dass die Variable gar kein anderer Datentyp als String sein kann?

[26:08] Und das muss ich nicht. Das kann die Sprache, beziehungsweise der Compiler für mich prüfen und das ist die Typ-Inferenz.

[26:13] Das heißt, der Compiler guckt auf der rechten Seite der Zuweisung. Aha, da steht ein String.

[26:17] Das heißt, die Variable auf der linken Seite muss ein String sein und dann musst du es auch nicht mehr explizit hinschreiben.

[26:23] Achtung, das heißt nicht, dass der Typ nicht festgelegt ist. Wir bleiben in der statischen Typisierung. Das ist ganz wichtig, wenn du dir seit Java 11

[26:30] gibt es die Typ-Inferenz für lokale Variablen. So was hinschreibst wie var a

[26:36] gleich Text in Anführungszeichen, dann heißt das var, also variable, V-A-R, die Abkürzung, nicht, dass du jetzt eine

[26:43] dynamische Variable oder sowas hast, die ihren Typ ändert. Nein, nein, nein, das ist auf jeden Fall ein String. Du musst es bloß nicht

[26:48] hinschreiben. Du sparst dir also Tipparbeit und genau darum geht es eigentlich.

[26:53] Alles, was du nicht hinschreiben musst, weil der Compiler es für dich ableiten kann, macht dir das Leben einfacher.

[26:58] Und deswegen ist die Typ-Inferenz ganz cool. Du musst deine Variablen eigentlich nur noch mit var

[27:02] deklarieren und dann wird der Typ dieser Variablen automatisch abgeleitet, dann aber auch in Stein gemeißelt. Also a

[27:09] ist danach ein String, auch wenn es nicht explizit da steht, aber du kannst dann nicht in der nächsten Zeile machen

[27:14] a gleich 5 oder so und ein Integer reinschreiben. Also ist ganz normal statisch typisiert. Du musst es bloß nicht hinschreiben und das ist ja genau diese

[27:22] Definition von

[27:23] Impliziter Ableitung des Datentypen. Implizit heißt immer, das steht nicht explizit im Code. Du musst nicht hinschreiben String a,

[27:31] sondern da steht einfach var a und String wird quasi abgeleitet, ohne dass du es siehst. Das ist quasi unsichtbar und der Compiler leitet das

[27:39] für dich ab.

[27:41] So, dann haben wir noch so ein paar schöne neue Datenstrukturen oder neu, weiß ich nicht, aber diesen Begriff hast du vielleicht noch nicht gehört.

[27:48] Algebraische Datentypen. Das hört sich natürlich wieder richtig geil an. Kannst du Leute mit beeindrucken, aber letztlich ist es

[27:53] eigentlich ganz einfach. Es gibt zwei Untertypen, und zwar die Produkttypen und die Summentypen.

[27:58] Deswegen auch Algebra. Produkt und Summe kennst du aus der Algebra, aus der Schule. Und ein Produkttyp, das ist letztlich nichts anderes als unsere

[28:06] Objekte, die wir schon aus der Objektorientierung kennen, beziehungsweise

[28:09] Structs oder Records oder wie auch immer die in anderen Sprachen heißen. Und das ist im Prinzip eine Definition des Datentyps durch die

[28:16] Kombination verschiedener anderer Datentypen. Wenn du zum Beispiel sagst, ein Auto, das ist ein eigener Datentyp, der besteht aus einem String,

[28:23] Marke und einem Integer, Kilometerstand, dann hast du String und Integer jetzt quasi

[28:28] kombiniert, quasi zu einem Produkt gemacht und daraus dann deinen eigenen Datentyp Auto

[28:33] definiert. Und Produkt deswegen, weil jetzt quasi der Datentyp definiert, dass du verschiedene Werte dieser unterschiedlichen Datentypen in

[28:41] deinem Auto drin miteinander kombinieren kannst. Deswegen heißt das Produkt. Also du könntest jetzt sagen, du hast einen VW mit Kilometerstand

[28:48] 3 und ein Mercedes mit Kilometerstand 5000 oder was auch immer.

[28:52] Durch diese Kombination wird der Datentyp als Produkttyp definiert.

[28:57] Und der nächste Typ, das ist der Summentyp und den gibt es so in Java zumindest noch nicht, in anderen Sprachen aber schon.

[29:04] Und da wird der Datentyp definiert als Liste möglicher

[29:07] Alternativen, beziehungsweise sogenannter

[29:10] Varianten. Das hast du vielleicht in anderen Sprachen schon mal gesehen. Das ist nicht das gleiche wie eine Enumeration. Das ist ein Aufzählungstyp, wo du sagst,

[29:17] Wochentage sind und dann hast du eine Liste erlaubter Werte, Montag, Dienstag, Mittwoch und so weiter. Das ist nicht gemeint, sondern es geht wirklich um

[29:24] einen Datentyp, wo du sagen kannst, der Datentyp ist entweder Typ A oder Typ B oder Typ C.

[29:30] Ich habe mal ein Beispiel aus der Versicherung mitgebracht. Du könntest sagen, ich definiere den Datentyp Krankenversicherung als entweder eine

[29:37] gesetzliche Krankenversicherung oder eine private Krankenversicherung.

[29:41] Diese beiden Klassen müssen auch nichts miteinander zu tun haben. Die müssen nicht in einer Vererbungsbeziehung stehen oder sonstiges oder ein Interface implementieren. Das können

[29:49] beliebige Klassen sein und du definierst halt einen Datentyp, der einen beliebigen Datentyp dieser Liste

[29:56] annehmen kann. Wofür braucht man das? Das ist eine gute Frage. Im Buch wird das Ganze mit einem Beispiel

[30:03] eines Ausdrucks aus der Programmiersprache gemacht. Wenn du dir einen bultschen Ausdruck mal vorstellst, kennst du aus der Programmierung

[30:09] True, zum Beispiel, wäre ein bultscher Ausdruck. Oder

[30:13] A größer 5 ist auch ein bultscher Ausdruck, weil immer ein bultsches Ergebnis rauskommt. Und daran siehst du schon, für den Compiler bzw.

[30:20] den Parser des Codes

[30:22] ist ein bultscher Ausdruck True

[30:24] genauso ein bultscher Ausdruck wie eben A größer 5, obwohl die beiden Ausdrücke an sich ja relativ unterschiedlich sind. Das eine ist ein Literal,

[30:31] True. Das andere ist eine Kombination aus einem Vergleich einer Variablen mit einem

[30:36] Literal, nämlich mit 5. Also True ist ein einziges Wort, wenn du so willst. Und A

[30:42] größer 5 ist eine Kombination, ein Ausdruck aus mehreren Bestandteilen. Trotzdem sind beide bultscher Ausdrücke. Das heißt, du könntest jetzt sagen, ein bultscher Ausdruck ist

[30:51] entweder ein Literal oder

[30:54] ein Ausdruck mit einem Integer und einem irgendwas und sonst wie. Das heißt, du kannst beliebige Datentypen

[31:00] quasi, ja, hintereinander schreiben und als Alternativen deklarieren und einem gemeinsamen

[31:06] sogenannten Summentyp dann eben zuordnen. Und dann kannst du in deinem Programm so was machen wie, hey, mein Summentyp gleich True.

[31:13] Das würde genauso gehen wie mein Summentyp gleich A größer 5.

[31:18] Und sowas ist insbesondere dann interessant, wenn ich Klassen zum Beispiel einem gemeinsamen Datentyp zuordnen will, die aber nicht irgendwie

[31:25] technisch verbinden möchte, zum Beispiel über ein gemeinsames Interface oder eine gemeinsame Basisklasse. Das ist nicht immer sinnvoll, ja.

[31:32] Mein Beispiel, was ich immer aus der Objektorientierung habe, ist ja Auto und Elefant. Beide kann ich waschen in einer Waschanlage, ja.

[31:38] Trotzdem haben die beiden natürlich keine gemeinsame Oberklasse. Wie soll ich das nennen?

[31:41] Ding und Auto erbt von Ding und Elefant erbt von Ding oder das ist irgendwie blöd, ne. Ich könnte in Java so was machen wie Interface

[31:48] waschbar. Dann sage ich, das Auto ist waschbar und auch der Elefant ist waschbar. Kann ich tun? Ja, das geht so ein bisschen in die Richtung.

[31:54] Ich könnte aber auch einfach sagen, hey, ich habe hier so ein

[31:57] waschbares Ding und das kann halt entweder ein Auto sein oder ein Elefant und dann müssen die beiden Klassen noch nicht mal irgendwas miteinander zu

[32:03] tun haben. Die müssen keine gemeinsamen Methoden haben oder sonst irgendwas. Durch diesen Summentyp bin ich da völlig frei in der Deklaration, welche

[32:10] unterschiedlichen Ausprägungen dieser Typ dann haben kann.

[32:13] So, das nächste Thema, was erläutert wird, ist das Pattern-Matching und das passt jetzt sehr gut zu diesen Datentypen, wie gerade zum Summentyp.

[32:20] Denn wenn ich jetzt mir den Code mal angucke und ich arbeite mit so einem Summentyp und da kann entweder ein Auto drinstehen oder

[32:26] ein Elefant,

[32:28] wie soll ich denn dann damit umgehen? Denn ein Elefant muss ich ja ganz anders waschen als ein Auto.

[32:31] Ja, es ist ein seltsames Beispiel, aber beim Auto zum Beispiel muss ich erst die Fenster zumachen und dann gehe ich von draußen mit einem heißen

[32:37] Dampfstrahler dran oder so.

[32:38] Beim Elefant geht das vielleicht nicht. Da muss ich einen Schwamm nehmen, weil er sich erschreckt, wenn ich den irgendwie mit so einem Dampfstrahler

[32:43] anpuste und ich muss vielleicht erst seine

[32:46] Pfoten, wollte ich schon sagen, wie heißen die denn? Seine großen Füße

[32:49] waschen, ja, und dann vielleicht einmal

[32:51] macht er den Rest mit dem Rüssel selber, was weiß ich, ja? Das heißt, die Implementierung ist ja komplett unterschiedlich, je nachdem, was ich da

[32:57] waschen möchte. Und darauf muss ich halt in meiner Programmiersprache reagieren können. Das heißt, ich muss jetzt entscheiden,

[33:02] hey, das Ding, was ich gerade waschen soll, was ist denn das jetzt? Ein Auto oder ist das ein Elefant? Und abhängig davon verhält sich mein

[33:08] Code unterschiedlich. Und das Ganze als Pattern-Matching umgesetzt, kann man sich jetzt so vorstellen, dass du im Prinzip ein großes Switch-Statement hast

[33:16] und sowas sagst wie, hey, wenn der Datentyp ein Auto ist, mach das und das,

[33:20] wenn der Datentyp ein Elefant ist, mach das und das, und wenn er nichts von beiden ist, dann schmeiß einen Fehler, zum Beispiel.

[33:26] Das könntest du auch in Java heute schon machen mit einem Switch-Statement, ja?

[33:30] Im Prinzip ist es das Gleiche, aber Pattern-Matching macht jetzt noch ein bisschen mehr.

[33:34] Denn wenn du zum Beispiel, wenn du das Auto waschen willst, vorher wissen musst, wie hoch es ist, ja, dann könntest du in diesem

[33:40] Pattern-Matching sogar sagen, wenn das Ding ein Auto ist und die Höhe

[33:45] größer 1,50 Meter, dann mach das und das, wenn das Ding ein Auto ist und die Höhe kleiner

[33:51] 1,50 Meter, dann mach das und das. Das heißt, du kannst nicht nur die Typen unterscheiden in diesem Switch-Statement, sondern du kannst in die konkreten Objekte auch

[33:59] reingucken und nicht nur sagen, welche Klasse ist das, sondern welche Werte haben die einzelnen Attribute dieses Objekts zum Beispiel, um dann

[34:07] darauf aufbauend eine Fallunterscheidung zu machen. Das heißt, dieses Pattern-Matching, das zerlegt die Objekte wirklich quasi in seine Bestandteile,

[34:14] kann das dann auch automatisch einzelne neuen Variablen zuordnen. Wenn du zum Beispiel die Höhe einer Variablen brauchst und damit dann irgendwas zu tun,

[34:21] macht das Pattern-Matching das automatisch, ja, liest die Höhe aus aus dem Objekt und legt dir eine neue Variable an, wo die Höhe drin steht.

[34:26] Das heißt, kannst du alles in einer Zeile Code quasi machen, um diese Objekte auseinander zu friemeln, um dann damit

[34:32] weiterzuarbeiten. Das heißt, hört man auch immer gerne sowas wie, das ist ein Switch-Statement on Steroids.

[34:37] Das heißt, das Ding kann viel, viel, viel mehr als einfach nur, wenn eins, mach das, wenn zwei, mach das, sondern wie gesagt, er geht in die

[34:44] Objekte rein und kann die auseinandernehmen.

[34:47] So, dann das nächste Thema, Generics oder auch generische Klassen. Schon seit Jahren eigentlich ein beliebtes Prüfungsthema.

[34:54] Trotzdem gibt es viele, die damit nicht so ganz

[34:56] umgehen können und vor allem nicht erklären können, was es ist. Grundsätzlich sind Daten, sind

[35:01] generische Klassen ja einfach Klassen, die, wie der Name sagt, generisch, also allgemein gültige

[35:06] Algorithmen implementieren. Schönes Beispiel aus Java oder auch aus so ziemlich allen anderen Objektnotierten Sprachen ist eine Liste. In eine Liste kann ich

[35:13] alle möglichen Objekte reinpacken, zum Beispiel Autos, Elefanten, Strings, Integer, was auch immer, ja.

[35:20] Und trotzdem macht die Liste immer das Gleiche, nämlich, die enthält mehrere Elemente dieses Datentyps.

[35:25] Und jetzt müsste ich, wenn ich sowas nicht machen könnte, eigentlich für jeden meiner Datentypen eine eigene Liste implementieren, also

[35:31] Elefantenliste, Autoliste, Integerliste, denn das muss ja statisch typisiert sein.

[35:36] Und diese Klassen, die ich da bauen würde, wären exakt identisch, nur dass sich der Datentyp, mit dem sie arbeiten, als inneres Element

[35:42] unterscheiden würde. Und das wäre ein riesen Overhead. Deswegen bieten eigentlich alle guten, statisch typisierten Sprachen solche Generics an.

[35:50] Nicht alle, ich habe schon gehört, Go, glaube ich, bietet das nicht, aber C Sharp und Java hat es auf jeden Fall.

[35:55] Denn dort kann ich einfach sagen, ich implementiere die Liste ein einziges Mal und statt zu sagen, in der Liste sind nur Integer drin, sage ich

[36:01] einfach, es ist irgendein Typ in dieser Liste drin. Und diesem Typen gebe ich dann einen Platzhalter, das wird dann meistens T

[36:08] genannt, deswegen heißt die Liste dann auch nicht mehr einfach nur List, sondern List of T und T steht dann so in Spitzenklammern

[36:14] dahinter. Das bedeutet, diese Klasse kann mit einem anderen Datentyp

[36:19] parametriert werden. Deswegen heißen Generics auch

[36:23] parametrierbare oder

[36:25] parametrisierbare, je nachdem, wie man das aussprechen will, Klassen. Das heißt, du definierst eine Klasse Liste, die mit beliebigen Datentypen umgehen kann.

[36:31] Und wenn du jetzt eine konkrete Liste benutzen willst, dann sagst du ihr, hey, du kannst bitte nur Integer

[36:37] aufnehmen. Und ab dann sind auch alle Methoden

[36:40] typsicher, geben dir Integer zurück, erwarten Integer als Parameter und so weiter. Aber wenn du dir die Klasse selber anguckst, die List of T,

[36:48] dann steht da nirgends so was von Integer oder String oder sonst wo, sondern nur dieser Platzhalter für den konkreten Datentypen, also T meistens.

[36:56] So, diese Generics sparen dir einen Haufen Arbeit, weil du

[37:00] ja, das Beste aus beiden Welten hast. Du hast die statische Typisierung, du hast eine Liste, in die du nur

[37:05] Integer packen kannst und der Compiler verhindert was anderes. Auf der anderen Seite musst du nicht extra die Integer-Liste

[37:11] implementieren, sondern kannst die Liste einmalig für alle beliebigen Datentypen

[37:14] implementieren. Das ist eine schöne Geschichte und das sollte man auf jeden Fall verstanden haben und vermutlich nutzt du das sowieso schon seit

[37:21] Jahren, wenn du schon länger programmierst in deiner Programmierung.

[37:24] Und wenn du irgendwie ArrayListOfT zum Beispiel benutzt oder in C-Sharp heißt das Ding ja direkt ListOfT.

[37:31] Das solltest du auf jeden Fall verstanden haben, was dahinter steckt.

[37:34] Und im Zusammenhang mit diesen generischen Klassen wird jetzt im Buch auch Co- und Kontravarianz erklärt und das sind

[37:40] ja

[37:41] richtig coole Sachen. Wenn man die gut erklären kann als Prüfling, dann hast du auf jeden Fall ein Stein im Brett bei den Prüfern.

[37:47] Und zwar geht es hier darum, wenn wir bei den generischen Klassen mal bleiben, wie sich

[37:52] verschiedene generische Klassen, die typisiert sind mit diesen diesen Datentypen, die ich da

[37:58] reingebe, wie die sich untereinander verhalten, ob die quasi

[38:02] ersetzbar sind durcheinander, also quasi ob die in einer Vererbungsbeziehung stehen. Ich erkläre es mal einem konkreten Beispiel.

[38:08] Angenommen, du hast eine Liste aus

[38:10] Personen und du hast eine Liste aus

[38:12] Studenten. Studenten sind Personen. Wenn dazwischen eine Vererbungsbeziehung besteht, würdest du sagen class Student extends

[38:19] Person. Das heißt, Student ist eine abgeleitete Klasse von Person.

[38:24] Steht in einer Vererbungsbeziehung. Wenn du dir jetzt anguckst, Liste von Person und Liste aus

[38:30] Studenten. Stehen die auch in einer Vererbungsbeziehung? Wenn der Typ-Parameter in einer Vererbungsbeziehung steht,

[38:36] Student erbt von Person, erbt dann auch die Liste von Student, von der Liste aus Person.

[38:41] Und das ist leider nicht der Fall. Zumindest nicht in Java. In anderen Polymer-Sprachen ist das so. Und genau darüber nähern wir uns jetzt

[38:47] Ko- und Kontra-Varianz. Wenn das so wäre,

[38:51] dass die Liste aus Studenten von Liste aus Personen erben würde, dann würden wir von einer

[38:58] Ko-Varianz sprechen. Ko-Varianz deswegen, weil die Vererbungsbeziehung des

[39:03] parametrierten Typs, das ist Student und Person, weil diese Vererbungsbeziehung in die gleiche Richtung geht, wie die Vererbungsbeziehung zwischen den Listen.

[39:11] Also List of Student erbt von List of Person,

[39:16] weil Student von Person erbt. Also die gleiche Vererbungsrichtung.

[39:21] Wenn das umgekehrt wäre, würden wir von Kontra-Varianz sprechen. Das heißt, wir hätten sowas wie die List of Person

[39:28] erbt von List of Student.

[39:31] Weil student von person erbt, also student person und list of student person, genau umgekehrte

[39:38] Vererbungsbeziehung, das wäre dann die contra-Varianz. Und co heißt ja immer gemeinsam in die

[39:43] gleiche Richtung und contra ist immer dagegen, also in die Gegenrichtung ist die Vererbung

[39:47] dann implementiert. Übrigens, wenn du dich jetzt gerade gewundert hast, dass die beiden

[39:51] Listen nicht in einer Vererbungsbeziehung stehen, dann wäre es dringend an der Zeit,

[39:54] das mal selber auszuprobieren. Du kannst es direkt nachvollziehen in Java. Wenn du so

[39:58] was schreiben würdest wie list of person gleich new list of student, dann sagt der

[40:04] Compiler, mähp, geht nicht. Das heißt, auch wenn die parametrierten Typen in einer

[40:08] Vererbungsbeziehung stehen, die Listen stehen nicht in einer Vererbungsbeziehung. Das ist

[40:13] der Standard in Java. Warum das so ist, das kann man sich am besten einem Beispiel erklären. Und

[40:19] ich mache jetzt mal nicht co und contra-Varianz beides, weil ich habe schon über 40 Minuten hier

[40:23] geschnackt. Das kannst du besser im Buch nachlesen. Aber ein Beispiel für die co-Varianz,

[40:27] warum selbst die co-Varianz nicht funktioniert. Bei contra-Varianz könnte man noch sagen,

[40:31] ja okay, die Vererbung ist ja völlig verdreht. Was soll das für einen Sinn ergeben? Aber die

[40:35] co-Varianz, da könnte man eigentlich meinen, klar, wenn die voneinander erben, warum sollen

[40:39] die Listen nicht voneinander erben? Aber ein einfaches Beispiel zeigt, dass das nicht sein

[40:43] darf. Nehmen wir das Beispiel mit Person und Student. Wenn du sagst, ich definiere mir eine

[40:48] Liste aus Personen ganz allgemein und weist dir aber eine Liste aus Studenten zu. Das wäre ja

[40:55] co-Variant. Person und Student. Student erbt von Person, also muss die Liste der Studenten auch

[41:02] von der Liste aus Personen erben. Hört sich erstmal sinnvoll an. Angenommen das ginge aber,

[41:06] dann hätte deine Variable den Typ List aus Personen. Die konkrete Implementierung wäre

[41:13] aber eine List aus Studenten. Wenn du jetzt aber mit der Variable arbeitest, wo nur List of Person

[41:19] steht, dann kannst du dieser Liste ja auch eine Person hinzufügen und das muss ja nicht zwangsläufig

[41:24] ein Student sein, das könnte ja vielleicht auch ein Lehrer sein oder ein Professor, wenn das auch

[41:29] eine Person ist. Und jetzt würde folgendes passieren. Die Variable sagt, ich darf Personen

[41:34] aufnehmen, also kannst du auch einen Professor hinzufügen. Sollte logisch sein. Die konkrete

[41:39] Implementierung hinter der Variablen ist aber ja eine List aus Studenten und wenn du dir jetzt

[41:43] einen Professor reingibst, dann sagt die Studentenliste, was willst du denn? Ich kann

[41:47] aber nur Studenten aufnehmen. Und da siehst du schon, dass selbst die co-Varianz so nicht

[41:52] funktionieren kann, weil die Listen intern mit diesen Datentypen nicht umgehen können. Du kannst

[41:57] in die Studentenliste einfach keinen Professor packen, aber wenn die Variable sagt, ich erlaube

[42:01] jede beliebige Person, dann passt das nicht zusammen. Und genau deswegen erlaubt der Java

[42:05] Compiler das auch nicht. Du kannst das so nicht machen in Java, außer du kennst die Syntax, um die

[42:15] Parameter so zu erweitern, dass das erlaubt ist. Und das sind die sogenannten Typ-Constraints. Da

[42:21] könntest du sowas hinschreiben wie, hey, ich habe hier eine List aus Personen oder irgendeinem

[42:27] abgeleiteten Typen. Und das würde dann so aussehen, dass du hast eine List of Fragezeichen

[42:33] ExtentsPerson. Wenn du das noch nie gesehen hast, das ist die offizielle Syntax, um in Java quasi

[42:40] Co-Varianz herzustellen. Deine Liste enthält irgendeine Klasse, die Person ist oder sie erweitert.

[42:48] Also Fragezeichen ExtentsPerson, Person selbst oder irgendwas abgeleitetes. Und das wäre in diesem

[42:53] Fall zum Beispiel der Student. Und dann würde das ganze Co-Variant funktionieren. Und auch für

[42:58] die Kontra-Varianz, dass du das wenigstens schon mal gehört hast, das wäre in Java dann

[43:02] Fragezeichen SuperPerson. Das wäre also irgendwas, was Person ist oder drüber. Das heißt in Java

[43:09] zum Beispiel Object. Alles erbt von Object. Das heißt, da könntest du auch irgendein beliebiges

[43:12] Object nutzen anstatt der Person. So, das waren die allgemeinen Sprachgrundlagen noch mal, die

[43:18] wichtig sind, um dann den Rest zu verstehen. Im Prinzip zu verstehen, wie man das dann in Java

[43:21] umsetzt und in anderen Objektorientierten Sprachen. Und jetzt kommen wir zu dem Prinzip hier in der

[43:25] funktionalen Programmierung. Das wäre jetzt sowas wie, was sind die drei Säulen der Objektorientierung?

[43:30] Vererbung, Polymorphik, Kapselung. Und jetzt kommen wir zu den Themen, was sind eigentlich die

[43:34] grundlegenden Prinzipien der funktionalen Programmierung? Das sollte man zumindest

[43:38] auflisten und auch grob erklären können, wie ich finde. Und das sind der Reihe nach Funktionsobjekte,

[43:43] reine Funktionen, Funktionen höherer Ordnung, Bedarfsauswertung, Unveränderbarkeit, Rekursion

[43:50] und referenzielle Transparenz. Nicht referenzielle Integrität, das kommt aus dem Bereich der

[43:57] Datenbanken. Hier reden wir von referenzieller Transparenz. Fangen wir mal ganz oben an.

[44:01] Funktionsobjekte oder auf Englisch auch First Class Citizens oder Functions as First Class Citizens

[44:07] genannt. Das bedeutet, dass die Funktionen fester Bestandteil der Sprache sind und genauso benutzt

[44:13] werden können wie zum Beispiel Klassen und primitive Datentypen und so weiter. Das heißt,

[44:17] ich kann die Variablen zuordnen, ich kann die als Parameter irgendwo übergeben. Ich kann damit tun

[44:23] und lassen, was ich will. Genauso wie ich es in Java zum Beispiel mit Objekten mache, kann ich es

[44:28] hier auch mit Funktionen machen. Das heißt, das ist als Sprachkonstrukt wirklich in die Sprache

[44:34] eingebaut und nicht irgendwie was, was drangeflanscht ist. Und da muss man jetzt zum Beispiel bei Java

[44:39] sagen, das ist ein bisschen drangeflanscht, denn diese Funktionen, diese Lambda-Ausdrücke, da

[44:43] werden im Hintergrund einfach anonyme Klassen generiert. Das heißt, die haben jetzt nicht

[44:46] einfach komplett die Sprache auf Funktional umgestellt, sondern die Lambda-Syntax sorgt

[44:51] nur dafür, dass eine anonyme Klasse mit einer Methode generiert wird und diese Klasse wird dann

[44:56] quasi als Datentyp benutzt. Also die haben es wirklich ein bisschen drangeflanscht, aber andere

[45:00] Sprachen haben halt wirklich das Prinzip einer Funktion in der Klasse selber integriert und da

[45:08] musst du also nicht so hinten rum doch eine Klasse definieren, sondern du kannst einfach sagen, ich

[45:12] will eine Funktion haben, zack, und der ganze Rumpf drumherum, der ist nicht notwendig, weil die

[45:17] Funktion halt eben ein gebauter Bestandteil der Sprache ist. So, dann reden wir von reinen

[45:22] Funktionen. Das geht so ein bisschen in die Richtung, wie wir programmieren wollen in der

[45:25] funktionalen Programmierung. Wir wollen nämlich möglichst ohne Seiteneffekte programmieren. Was

[45:30] sind Seiteneffekte? Das sind Dinge, die du als Aufrufer der Methode nicht sehen kannst. Das heißt,

[45:36] mein Lieblingsbeispiel in Java ist System.out.println. Ich gebe was auf der Konsole aus. Die

[45:40] Funktion, ach, die Methode println hat aber einen Void-Rückgabewert. Das heißt, es kommt nicht

[45:44] zurück. Das heißt, ich als Aufrufer kriege überhaupt nicht mit, ob der Text jetzt wirklich

[45:48] ausgegeben wurde. Ich kriege gar nichts zurück. Und als Seiteneffekt erscheint irgendwo auf der

[45:53] Konsole hoffentlich ein Text. Und diese Seiteneffekte, die wollen wir in der

[45:58] funktionalen Programmierung überhaupt nicht haben. Wir wollen stattdessen, dass sich Funktionen immer

[46:03] identisch verhalten, wenn sie identische Eingangswerte bekommen für ihre Parameter. Wenn

[46:08] ich also mal ein Beispiel habe, was nicht seiteneffektfrei ist, ein Objekt, was irgendwie

[46:13] einen Zähler intern hat. Und jedes Mal, wenn ich eine Funktion aufrufe, wird dieser Zähler erhöht.

[46:17] Das heißt ja, bei jedem Funktionsaufruf wäre der Wert ein anderer. Also nicht der gleiche,

[46:22] wenn ich den gleichen Parameter habe. Und das ist genau so etwas, was wir nicht haben wollen.

[46:27] Also Zustand in der Funktion, der führt genau dazu, dass ich eben keine reine Funktion habe,

[46:33] weil das Ergebnis der Funktion nicht ausschließlich davon abhängt, was ich reingebe. Angenommen,

[46:38] ich hätte eine Addiere-Funktion, wo ich zwei Parameter habe, a und b, und es kommt einfach a

[46:42] plus b zurück. Dann wäre das eine reine Funktion. Denn wenn ich 3 und 5 reingebe, kommt immer 8 raus,

[46:48] egal wie oft ich das mache und ob ich vorher schon was anderes aufgerufen habe. Es gibt keinen

[46:52] Seiteneffekt. Wenn ich aber zum Beispiel sowas habe, wie dieses Objekt, was ich eben beschrieben

[46:56] habe, was sich ein intern Zähler merkt und einfach bei jeder Addition diesen Zähler oben drauf

[47:00] rechnet, also anstatt zu sagen return a plus b, macht er noch return a plus b plus Zähler. Und

[47:07] der Zähler kann sich verändern. Dann ist eben nicht sichergestellt, dass 8 rauskommt, wenn ich

[47:11] 3 und 5 reingebe. Denn wenn der Zähler 2 ist, kommt 10 raus. Wenn der Zähler 3 ist, kommt 11

[47:16] raus. Und da ich den Zähler nicht mit reingebe, ich ihn also auch nicht sehen kann, sieht das für

[47:21] mich so aus, als wenn sich die Funktion unterschiedlich verhält. Und das tut sie ja

[47:23] auch, obwohl ich das Gleiche reingegeben habe, nämlich 3 und 5. Und das soll nicht so sein,

[47:28] denn solche Funktionen sind sehr schwer zu testen. Ich weiß nicht, was zurückkommen muss,

[47:32] weil ich ja nicht alles definieren kann, was ich reingebe. Und das ist in der funktionalen

[47:37] Formulierung nicht gut. Das wollen wir nicht haben. Wir wollen, dass die Funktionen ausschließlich

[47:41] mit dem arbeiten, was sie reinbekommen, also ihren Parametern, und mir dann immer das gleiche

[47:47] Ergebnis liefern, wenn ich auch die gleichen Eingangswerte reingebe. So, dann kommen wir zum

[47:52] nächsten Prinzip. Das sind die Funktionen höherer Ordnung oder auf Englisch function oder higher

[47:58] order function. Was bedeutet das? Das sind letztlich einfach nur Funktionen, die andere

[48:04] Funktionen als Parameter bekommen oder als Rückgabewert haben. Wenn ich also die Additionsfunktion

[48:11] von gerade nehme, da kommen einfach zwei Zahlen rein, A und B. Wenn ich jetzt aber eine Funktion

[48:15] habe, die als Parameter eine Funktion bekommt, und ein schönes Beispiel dafür wäre Map. Wenn

[48:20] du MapFilterReduce von Streams oder allgemein von der Verarbeitung mehrerer Elemente kennst,

[48:25] dann weißt du, dass die Map-Funktion eine andere Funktion als Input hat, und zwar, um aus dem

[48:30] Datentyp, der gerade in der Liste drin ist, einen anderen zu machen. Das heißt, angenommen, ich habe,

[48:34] wie eben, meine ListOfPerson, und ich möchte von diesen Persons immer alle Vornamen haben,

[48:40] dann muss ja aus der Person quasi ein String werden. Person liefert, wenn ich da irgendwie

[48:45] GetVornamen oder so aufrufe, ein String als Vornamen zurück. Und wenn ich jetzt eine Map-Funktion

[48:50] habe, wo ich sage, hey, ListOfPerson, Map, und da gebe ich eine Funktion rein, die aus der Person

[48:56] den Namen macht, dann habe ich als Ergebnis eine ListOfString. Das heißt, ListOfPerson, Map und

[49:02] dann sowas wie PersonGetFirstName, dann wird die Map-Funktion für jedes Element in der Liste

[49:07] GetFirstName aufrufen und das wieder in eine neue Liste packen. Und so habe ich aus der

[49:12] ListOfPerson eine ListOfString gemacht. Und damit das funktioniert, braucht diese Map-Funktion eine

[49:18] andere Funktion als Parameter, und zwar die, die aus der einen Person den einen String macht. Und

[49:24] die Map-Funktion kümmert sich dann darum, aus allen Personen alle Namen zu machen, indem sie

[49:29] quasi die Iteration macht und die übergebene Funktion auf jedes Element der Liste anwendet.

[49:34] So, mit diesem Prinzip der höherwertigen Funktionen oder higher-order functions kann

[49:39] ich jetzt Funktionen zu komplexeren oder abstrakteren Funktionen zusammenbauen, weil

[49:45] ich zum Beispiel wie die Map-Funktion nur ein einziges Mal definieren muss, wie ich über eine

[49:49] Liste iteriere und Werte umwandle, und ich kann dann von draußen bestimmen, wie diese

[49:54] Wertumwandlung stattfinden soll. Die nächste Funktion, die mir dann aus Personen ein Integer

[49:59] macht, weil es zum Beispiel das Alter abfragt, kann mit der gleichen Map-Funktion weiterarbeiten. Ich

[50:04] muss da also nichts anpassen. Ich muss nur einmal Map programmieren und kann dann beliebig

[50:09] andere Funktionen da reingeben. Das ist so ein bisschen, wie wir es eben gehört haben, mit den

[50:14] generischen Klassen. Den generischen Code, den programmiere ich auch nur einmal und dann ändere ich

[50:18] einfach den Datentyp an der Klasse. Und bei den Funktionen höherer Ordnung ist es so, ich

[50:22] programmiere auch einmal die Implementierung und dann kann ich das, was die Implementierung

[50:26] konkret machen soll, von draußen quasi austauschen, indem ich eine Funktion reingebe, die angewendet

[50:31] werden soll. Dadurch kann ich also sehr schön wiederverwendbare und abstrakte Funktionen

[50:37] definieren. Ein weiterer Punkt auf der Liste war die Bedarfsauswertung oder auf Englisch

[50:44] Lazy Evaluation. Was heißt das? Was ist da genau lazy, also faul? Man sagt ja immer, Programmierer

[50:52] sind faul. Ist auch gut so. Wir wollen uns das Leben möglichst einfach machen. Das hat hier aber

[50:57] ja nicht direkt etwas mit zu tun. Es geht hier eher darum, wenn ich einen Ausdruck auswerten

[51:02] möchte, dann soll das erst dann passieren, wenn ich auch wirklich den Wert brauche. Überlegen wir

[51:06] noch mal kurz, was ein Ausdruck ist. Kennst du hoffentlich noch aus der Programmierung, wenn ich

[51:09] sowas habe wie A plus B eben für eine Summe. Dieses A plus B ist ein Ausdruck, der ein Ergebnis

[51:15] produziert. In diesem Fall eben die Summe von A und B. Wenn ich jetzt diesen Ausdruck irgendwo

[51:21] programmiere, aber nie auf den Wert zugreife, warum soll der Computer dann A plus B überhaupt

[51:26] ausrechnen? Ich brauche sie eigentlich gar nicht in meinem Programm. Und genau das ist jetzt die

[51:30] Idee der Lazy Evaluation. Da wird die Auswertung dieser Ausdrücke so lange hinausgezögert, bis der

[51:37] Wert auch wirklich benutzt wird. Nehmen wir mal ein Beispiel. Angenommen, das würde gehen mit

[51:41] einer Summe. Der Computer berechnet Summen sofort. Aber angenommen, das würde gehen, dann hätte ich

[51:45] als A plus B einer Variablen zugeordnet. Und anstatt dass der Computer jetzt sagt, okay, ich

[51:49] berechne A plus B und weise den Wert der Variablen zu, weist der Computer erstmal nur diese Funktion

[51:54] A plus B der Variablen zu. Und irgendwann, 27 Zeilencode später, wenn ich dann zum ersten Mal

[52:00] auf das Ergebnis, also auf die Summe zugreife, weil ich die dann zum Beispiel auswerten will

[52:04] oder ausgeben will, erst dann wird die Addition wirklich durchgeführt. Also erst dann,

[52:09] wenn wirklich der Wert benötigt wird, wird die zugrunde liegende Funktion auch ausgeführt. Und

[52:14] das hat natürlich Vorteile, wenn ich zum Beispiel Funktionen habe, die nur bei Bedarf ausgeführt

[52:21] werden, weil zum Beispiel noch ein if oder sowas davor ist. Ich will das nur ausführen, wenn ein

[52:24] bestimmtes Kriterium greift. Dann wäre es ja blöd, wenn es einfach erstmal immer vorberechnet

[52:29] wird und dann erkannt wird, oh, if soll ich gar nicht machen, habe ich jetzt umsonst gemacht. So

[52:34] ist ein Beispiel. Oder wenn ich zum Beispiel mit Streams arbeite, die unendlich lang sind,

[52:38] ja, also eine unendlich lange Zahlenfolge, wenn ich die durch iterieren will, da könnte ein

[52:43] bisschen Länge dauern, nämlich eben unendlich lang. Und wenn jetzt aber der Stream sagt,

[52:47] ich mache das erst, wenn es wirklich angefragt ist, dann kann ich trotzdem über diesen Stream

[52:50] iterieren und zwar nur so lange, wie ich auch weiter Zahlen anfrage. Wenn ich irgendwann sage,

[52:55] jetzt reicht es mir, ich höre auf, dann hört der Stream halt einfach auf, aber er zählt nicht

[52:59] erstmal unendlich viele Zahlen auf, um dann nach 1000 aufzuhören zum Beispiel. Das würde ja auch

[53:04] gar nicht gehen, weil dann würde der Code unendlich lange laufen. Also für solche Sachen

[53:08] ist Lazy Evaluation sehr gut geeignet. Ich programmiere das erstmal, aber ausgeführt wird

[53:13] es erst dann, wenn ich wirklich auf den Wert zugreifen will. Dann haben wir noch ein weiteres

[53:18] wichtiges Prinzip und zwar die Unveränderbarkeit oder auf Englisch, das ist wahrscheinlich eher

[53:22] verbreitet, Immutability. Was hat es damit auf sich? Die Werte, mit denen wir arbeiten,

[53:30] sollen nicht mehr verändert werden. Das heißt, ganz klassische, wie wir programmieren lernen,

[53:34] mit Variablen, die ihre Werte ändern, das wollen wir überhaupt gar nicht haben. Stattdessen erzeugen

[53:40] wir immer neue Werte und lassen die alten, wie sie sind. Und das gilt auch für Datenstrukturen

[53:45] wie Listen. Wenn ich einer Liste ein Element hinzufüge, mache ich das nicht in der Liste,

[53:49] die dann eins länger wird, sondern ich erzeuge eine komplett neue Liste mit dem Inhalt der alten

[53:54] plus dem neuen Element. Was ist der Vorteil? Wofür brauche ich sowas? Das ist dann sinnvoll,

[53:59] insbesondere wenn ich parallele Verarbeitung mache. Stellen wir uns vor, wir haben zwei Threads,

[54:03] die auf einer gemeinsamen Liste arbeiten und der eine Thread löscht da drin rum oder fügt

[54:08] was hinzu oder wie auch immer und der zweite Thread auch, dann kommen die sich hundertpro

[54:11] irgendwann in die Quere. Die löschen vielleicht oder versuchen das gleiche Element zu löschen

[54:15] oder der eine löscht es, der andere will es drin haben oder wie auch immer. Das heißt,

[54:19] das geht gar nicht. Stattdessen wollen wir unveränderbare Listen haben. Das heißt,

[54:23] jeder Thread hat quasi seine eigene, ich sag jetzt mal, Kopie der Liste, die unveränderlich ist und

[54:29] da drin kann er machen, was er will, weil es keine Auswirkung auf den anderen Thread hat,

[54:33] der auch gerade damit arbeitet. Das lässt sich in funktionalen Sprachen sehr gut umsetzen,

[54:39] weil die Datenstrukturen entsprechend vorbereitet sind. In Java und anderen Sprachen ist das nicht

[54:44] ganz so. Also wenn du eine Arrayliste sagst, add, dann fügt die sich selbst ein Element hinzu und

[54:49] ist danach eins länger. Ja, was wir jetzt aber eigentlich bräuchten ist, erzeug eine neue Liste,

[54:53] kopiere alle Elemente in die neue Liste und füge das neue Element hinten dran und lasse die alte

[54:58] Liste unverändert. Da kannst du dir schon vorstellen, dass das ziemlich speicherineffizient

[55:02] ist, weil du auf einmal die doppelte Speichermenge hast. Du hast zwei Listen im Speicher auf einmal,

[55:05] die alte und die um eins längere. Das ist natürlich sehr ungünstig, deswegen machen

[55:10] funktionalen Sprachen das ein bisschen anders. Die arbeiten dann intern mit bestimmten Datenstrukturen

[55:14] darauf optimiert sind. Die müssen also nicht die ganze Liste rüber kopieren, sondern die haben eine

[55:17] Art Zeiger auf die alte Liste und die neue Liste ist dann quasi das eine Element plus die alte Liste.

[55:23] Das geht aber leider in Java nicht. Es gibt allerdings Funktionsbibliotheken, die sowas

[55:27] erweitern, wie zum Beispiel Waver, so der Standard eigentlich für funktionale Programmierung. Da gibt

[55:32] es solche unveränderbaren Listen, mit denen du dann arbeiten kannst. Ja, ich guck mal. Oh Gott,

[55:37] ich muss mal ein bisschen hinmachen hier. Schon fast eine Stunde voll und ich habe noch einen

[55:40] ganzen Haufen Themen. Machen wir weiter mit Rekursion. Rekursion kennst du bestimmt schon

[55:44] aus der ganz normalen Programmierung. Eine Funktion, die sich immer wieder selber aufruft.

[55:47] Du weißt allerdings, dass das in normalen Programmiersprachen nicht so gut ist, weil jede

[55:51] Funktion immer auf den Stack gelegt wird und irgendwann ist der Stack voll und dann knallt es.

[55:56] Dann gibt es den berühmten Stack Overflow. Funktionale Programmiersprachen sind soweit

[55:59] vorbereitet, dass sie quasi aus einer rekursiven Programmierung automatisch eine Iteration machen.

[56:04] Das heißt, du programmierst einen rekursiven Aufruf in deinem Code, aber der Compiler erkennt,

[56:10] oh, was er hier eigentlich machen will, ist einfach nur mit dem nächsten Wert quasi nochmal

[56:13] die Funktion aufrufen und macht daraus quasi automatisch eine Iteration, also eine Schleife

[56:18] zum Beispiel. Das heißt, es kann nicht zu einem Stack Overflow kommen. Das ist in Java nicht so.

[56:22] Wenn du einen rekursiven Aufruf machst, ist das ein rekursiver Aufruf, der wird auch nicht

[56:25] automatisch umgewandelt in irgendwas. Das heißt, in Java funktioniert das so nicht,

[56:29] aber in anderen Sprachen funktioniert das. Und das Feature, auf das man achten muss,

[56:32] ist die sogenannte Tail Call Optimization. Das heißt, wenn der allerletzte Befehl in

[56:38] deiner Methode der rekursive Aufruf der Methode selbst ist, dann kann die Programmiersprache das

[56:43] in eine Iteration umwandeln und dann entsprechend auch mit, ja, ich will jetzt nicht sagen unendlicher

[56:49] Rekursion arbeiten, aber auf jeden Fall, du kannst deine Rekursion so benutzen, als wäre es eine ganz

[56:53] normale Schleife und damit auch bis eine Milliarde zählen und das wird den Computer nicht zum

[56:58] Absturz bringen. Wenn du das in Java machst, ist nicht nur bei einer Milliarde Schluss,

[57:02] sondern schon deutlich früher, je nachdem, was dein Speicher so hergibt, aber ich schätze jetzt

[57:06] einfach mal maximal ein paar tausend rekursive Aufrufe werden funktionieren. Danach ist Schluss,

[57:11] tendenziell sogar eher weniger, also vielleicht sogar nur ein paar hundert. Also in Java programmieren

[57:16] wir nicht rekursiv, in der richtigen funktionalen Sprache machen wir das aber, weil das der Standard

[57:20] ist, um zu iterieren. Jetzt fragt man sich, warum? Warum nämlich einfach Schleifen? Weil Schleifen

[57:25] Mutable State haben und genau dieser Unveränderbarkeit, der Immutability widersprechen.

[57:31] Denn wenn du iterierst, brauchst du eigentlich immer eine Iterationsvariable. Nimm die klassische

[57:35] Vorschleife, da hast du I als Iterationsvariable und was macht die Schleife? Rechnet immer einen

[57:40] drauf auf I. Das heißt, I wird manipuliert, I wird verändert und das widerspricht der

[57:45] Immutability. Bei Rekursionen sagst du einfach, so, ich bin jetzt fertig, ruf bitte die gleiche

[57:50] Methode mit einem neuen, veränderten Parameter auf, aber dieser Parameter wird nicht in der

[57:56] aktuellen Iteration manipuliert, sondern erzeugt wirklich einen ganz neuen Methodenaufruf mit

[58:00] neuem Parameter. Das heißt, die aktuelle Iteration wird nicht beeinträchtigt, es gibt

[58:05] keinen geteilten Zustand, diese Iterationsvariable, sondern jeder Methodenaufruf steht allein für sich

[58:11] und kriegt seinen eigenen Wert rein und ist damit unveränderbar. Deswegen wird die Rekursion

[58:16] bevorzugt in der funktionalen Programmierung, weil die ganz normale Iteration mit Schleifen

[58:21] halt eben nicht Immutable ist. So, letzter Punkt der Prinzipien, referenzielle Transparenz. Was

[58:28] hat es damit auf sich? Wir haben schon gelernt, wenn wir eine reine Funktion haben, erzeugt die

[58:33] zu gegebenen Eingangsparametern immer das gleiche Ergebnis. Und das bedeutet jetzt, wenn wir das

[58:37] übertragen auf den Code, überall, wo ich eine Funktion mit bestimmten Parametern aufrufe,

[58:41] könnte ich theoretisch auch einfach das Ergebnis dieser Funktion einsetzen. Beispiel, statt zu

[58:47] schreiben addiere 3,5, könnte ich einfach direkt 8 hinschreiben, weil das Ergebnis wird ja nie anders

[58:53] sein. Wenn addiere eine pure, eine reine Funktion ist, wird 3 und 5 als Parameter immer 8 ergeben.

[59:00] Das heißt, ich kann 8 hinschreiben oder addiere 3,5, das ist egal, weil beides gleichwertig ist.

[59:06] Und das ist die Idee der referenziellen Transparenz. Ein Ausdruck kann jederzeit

[59:11] durch seinen Wert ersetzt werden, aber auch umgekehrt. Das hat jetzt den Vorteil, dass meine

[59:17] Programme zum Beispiel sehr viel verständlicher werden. Wenn ich den Ausdruck da sehe, dann weiß

[59:20] ich, was rauskommen muss. Es kann auch nicht aufgrund irgendeines Zustands was anderes jetzt

[59:25] rauskommen. Das heißt, es ist sehr viel einfacher nachzuvollziehen, zu welchem Ergebnis meine

[59:28] Programme kommen, weil es ja eindeutig ist, wenn ich da einen Ausdruck sehe, es kann nicht

[59:33] abhängig von irgendwelchen Zuständen, irgendwelchen Variablen auf einmal was anderes rauskommen,

[59:37] als das, was ich erwarte.

[59:40] So, dann mache ich mal weiter, auch wenn ich jetzt schon bei einer Stunde angekommen bin.

[59:43] Ich glaube, den Rest, den haue ich jetzt noch durch, weil ich finde das so wichtig.

[59:46] Das nächste Kapitel, was ich mir hier überlegt habe, ist funktionale Datenstrukturen.

[59:49] Und da hatte ich ja im Inhaltsverzeichnis des Buchs schon gesagt, Funktoren, Monoide und Monaden gibt es.

[59:55] Und die gehen wir mal im ganz fixen Schnelldurchlauf einmal durch.

[59:59] Wir fangen mit den Funktoren an.

[1:00:01] Und das sind einfach Datenstrukturen, die die Funktion Map anbieten.

[1:00:05] Map habe ich eben schon erklärt, was das macht.

[1:00:07] Wir haben ja meist generische Datentypen, wie zum Beispiel eine Liste aus Personen hatten wir gerade.

[1:00:12] Und wenn ich diese Liste umwandeln will in eine Liste aus den Vornamen dieser Personen, dann nehme ich dafür die Funktion Map.

[1:00:19] Und die Funktion Map ist eine Funktion höherer Ordnung, die kriegt eine andere Funktion rein.

[1:00:23] Und diese andere Funktion bekommt genau eine Person rein und gibt einen String zurück.

[1:00:28] Die wird also auf ein Element der Liste angewendet.

[1:00:32] Die Map-Funktion macht jetzt die Iteration und ruft diese übergebene Funktion für jedes Element der Liste auf und erzeugt daraus dann die Liste der Vornamen zum Beispiel.

[1:00:42] Und wenn ich so eine Datenstruktur habe, dann ist das ein sogenannter Funktor.

[1:00:47] Das heißt, ein Funktor hat die Map-Funktion, um den enthaltenen Datentypen in einen anderen Datentypen zu transformieren.

[1:00:54] Das nennt man Funktor.

[1:00:56] Beispiele in Java gibt es mehrere.

[1:00:58] Stream hatte ich schon genannt.

[1:00:59] Optional aber auch.

[1:01:00] Ich kann auch ein Optional, also ein Objekt, was entweder ein Element enthält oder nicht, das kann ich auch mit Map transformieren.

[1:01:10] Und wenn der Optional halt leer ist, dann ist er auch im Nachhinein leer.

[1:01:13] Aber wenn im Optional was drin war, dann ist danach was drin, was jetzt den neuen Typen hat.

[1:01:17] Also das Map-Prinzip kann ich auf verschiedene andere Datenstrukturen übertragen.

[1:01:21] Das sind jetzt alles sogenannte Funktoren.

[1:01:24] Nächster Punkt auf der Liste sind Monoide.

[1:01:28] Monoide.

[1:01:29] Damit kann man eine Menge von Elementen zu einem Gesamtergebnis verknüpfen.

[1:01:34] Also reduzieren, zu einem Wert reduzieren.

[1:01:38] Denken wir mal in SQL an unsere Aggregatsfunktion.

[1:01:41] Wenn du sowas machst wie select count from, dann ist das Ergebnis eigentlich eine Tabelle, in der Elemente stehen.

[1:01:47] Aber count reduziert diese Liste jetzt auf einen einzigen Wert, nämlich auf ihre Anzahl.

[1:01:53] Das gleiche würdest du mit sum zum Beispiel haben oder average oder sowas.

[1:01:56] Und das Ganze gibt es jetzt auch in Datenstrukturen.

[1:01:58] Das heißt, wenn du so etwas hast wie ich habe eine Liste und ich hätte gerne, bleib mir bei den Personen,

[1:02:03] ich hätte gerne das Durchschnittsalter aller Personen.

[1:02:06] Dann hast du eine Liste aus Personen, die reingeht und raus kommt ein Double zum Beispiel oder Int oder was auch immer.

[1:02:12] Also mehrere Werte rein, ein Wert raus und das Ganze wäre dann ein sogenannter Monoid.

[1:02:19] Und die Funktion, die dafür notwendig ist, die heißt in den funktionalen Sprachen eigentlich Fold.

[1:02:24] Also sowas wie zusammenklappen, weil das immer so funktioniert.

[1:02:28] Das wird elementpaarweise durchgegangen.

[1:02:31] Wenn du die Personen von eben dir noch mal als Beispiel nimmst,

[1:02:34] diese Funktion, die du jetzt der Fold-Funktion reingibst,

[1:02:37] guckt sich immer zwei Elemente der Liste an und ermittelt dann das Ergebnis.

[1:02:41] Also angenommen, wir würden die Summe von irgendwas berechnen,

[1:02:43] nimmt er die erste Person und die zweite und summiert beide Werte auf.

[1:02:47] Mit dem Ergebnis dieser Summe geht er dann in die nächste Person und summiert den Wert auf.

[1:02:53] Mit dem Ergebnis geht er wieder in die nächste Person und summiert den auf.

[1:02:55] Das heißt, es werden immer zwei Werte miteinander verbunden und dann wieder über die gesamte Liste drüber laufen lassen,

[1:03:02] sodass am Ende die Gesamtsumme aller Personen in der Liste rauskommt.

[1:03:06] Und dafür ist aber noch eins wichtig und zwar das sogenannte neutrale Element.

[1:03:11] Denn angenommen, wir nehmen die Summe, du musst ja mit irgendwas anfangen.

[1:03:14] Wenn du mit einer Person loslegst, das ist ja keine Zahl.

[1:03:17] Das heißt, du musst erstmal sagen, ich will vielleicht was auch immer das Alter summieren,

[1:03:20] auch wenn das nicht ganz so sinnvoll ist.

[1:03:22] Mit was summierst du denn die allererste Person in der Liste, wenn du noch nichts hast?

[1:03:27] Und genau dafür brauchst du das neutrale Element.

[1:03:29] Das ist tatsächlich genau das Ding, was du aus dem Mathematikunterricht noch kennst.

[1:03:32] Das neutrale Element der Addition ist die 0.

[1:03:35] Du kannst unendlich oft plus 0, plus 0, plus 0 hinter eine Summe schreiben

[1:03:39] und der Wert der Summe verändert sich nicht.

[1:03:41] Das neutrale Element der Multiplikation ist die 1,

[1:03:44] denn du kannst ganz oft machen mal 1, mal 1, mal 1, mal 1, ohne den Wert zu ändern.

[1:03:49] Du brauchst jetzt bei dieser Reduktion also ein neutrales Element,

[1:03:52] mit dem es losgeht, quasi dein Startwert,

[1:03:55] und dann die Funktion, die diesen Startwert bekommt,

[1:03:58] und das nächste Element aus der Liste, um dann den neuen,

[1:04:01] also weiterführenden Startwert zu berechnen.

[1:04:04] Beispiel für die Summe.

[1:04:06] Du gehst mit 0 rein und summierst das Alter der ersten Person drauf.

[1:04:10] Dann gehst du mit diesem gerade neu ausgerechneten Summe,

[1:04:14] mit der Summe aus 0 und dem Alter, in die nächste Person und addierst das Alter auf.

[1:04:18] Mit diesem neuen Wert gehst du in die nächste Person und summierst das nächste Alter auf,

[1:04:21] und so weiter und so fort.

[1:04:23] Das heißt, das neutrale Element ist einfach nur das erste Ding, was summiert wird,

[1:04:26] damit du nicht mit nichts startest, sondern quasi mit einem Startwert,

[1:04:29] und das muss das neutrale Element sein,

[1:04:32] damit du diese Summe nicht, oder diese Operation nicht manipulierst,

[1:04:36] sodass sie nicht mehr richtig ist.

[1:04:37] Also wenn du zum Beispiel mit 20 anfängst als Startwert,

[1:04:40] dann wäre das Durchschnittsalter von einer 10-jährigen Person auf einmal 30.

[1:04:44] Das geht ja nicht.

[1:04:46] Das heißt, du musst mit 0 oder mit 1 oder was auch immer anfangen.

[1:04:48] Auf jeden Fall dem neutralen Element der Operation.

[1:04:52] So, und vom Monoid kommen wir jetzt zum letzten, nämlich der Monade.

[1:04:56] Und eine Monade, darüber kann man alleine eine Stunde was erzählen.

[1:04:59] Habe ich übrigens auch schon mal gemacht, habe ich mal einen Vortrag darüber gehalten.

[1:05:02] Ist in den Shownotes alles verlinkt, kannst du mal reingucken.

[1:05:04] Die Folien, aber auch Artikelbeweise habe ich dazu mal geschrieben.

[1:05:08] Und eine Monade bietet im Prinzip zwei Funktionen an.

[1:05:11] Das kann man in Java nicht 1 zu 1 abbilden, aus verschiedenen Gründen.

[1:05:14] Das will ich jetzt hier nicht im Detail läutern, kannst du gerne in meinem Artikel nachlesen.

[1:05:17] Aber grundsätzlich geht es darum, dass die zwei Funktionen anbietet eine Monade.

[1:05:21] Und zwar eine Funktion, um aus einem beliebigen Datentyp eine Monade mit diesem Datentyp zu machen.

[1:05:27] Ganz einfaches Beispiel, optional off.

[1:05:30] Du hast ein String und du möchtest aus dem String ein optional off String machen.

[1:05:35] Dann rufst du auf optional.off und gibst da dein String rein.

[1:05:39] Und dann hebt die Monade den Basisdatentypen in den monadischen Datentypen.

[1:05:45] Du machst also aus dem String ein optional off String oder ein list off String oder ein stream off String oder was auch immer.

[1:05:53] Du hast jetzt also nicht mehr den Datentyp selbst, sondern du hast diese Monade da drumherum jetzt.

[1:05:59] Jetzt muss es noch eine weitere Methode geben in dieser Monade und zwar flatMap.

[1:06:03] Wenn es die gibt, dann haben wir eine Monade.

[1:06:05] Und was macht jetzt das flatMap?

[1:06:07] Das Map habe ich schon erklärt. Da wird aus dem inneren Datentyp ein anderer Datentyp gemacht. Fertig.

[1:06:13] Ein flatMap ist jetzt eine Funktion oder kriegt eine Funktion rein, die aus diesem inneren Datentyp eine Monade des neuen Datentyps macht.

[1:06:22] Das heißt, wenn du vorher einen String da drin hattest, kriegst du mit der Funktion, die du reingibst, jetzt nicht ein Integer,

[1:06:27] sondern ein list off Integer zum Beispiel oder ein optional off Integer oder wo auch immer du gerade drin bist.

[1:06:34] Angenommen, du hast ein optional off String, also es könnte ein String drin sein oder nicht.

[1:06:39] Und wenn du jetzt dessen Länge haben willst, dann brauchst du vielleicht eine Funktion, die aus dem String ein optional off Int macht.

[1:06:48] Beim Map würdest du aus dem String ein Int machen.

[1:06:52] Beim flatMap würdest du aus dem String ein optional off Int machen.

[1:06:57] Das heißt, das Ergebnis ist nicht der Datentyp, sondern die Monade mit diesem enthaltenen Datentyp.

[1:07:04] Und was du dann als Ergebnis hättest, wäre ein optional off optional off Int.

[1:07:10] Und das wäre irgendwie ungünstig.

[1:07:12] Beziehungsweise nicht off Int, übrigens off Integer, weil mit primitiven Datentypen funktionieren die Dinger nicht in Java.

[1:07:17] Aber zum Verständnis.

[1:07:19] Das heißt, wenn du jetzt aber nicht das optional off optional off Int haben willst, sondern den optional off Int,

[1:07:24] dann musst du quasi einen optional wieder flach klopfen.

[1:07:27] Und deswegen heißt das flatMap.

[1:07:29] Du hast quasi ein optional zu viel und den willst du weghaben.

[1:07:32] Deswegen machst du das flatMap.

[1:07:35] Ein Beispiel, damit das vielleicht noch etwas verständlicher wird, was mit dem optional gedöhnt.

[1:07:38] Das war glaube ich ein bisschen schwer nachzuvollziehen.

[1:07:40] Angenommen, du hast eine Liste aus Personen.

[1:07:43] Und diese Personen können Kurse belegen in der Uni zum Beispiel.

[1:07:47] Und jede Person kann mehrere Kurse belegen.

[1:07:50] Also zum Beispiel Englisch und Medizin oder sonst irgendwas.

[1:07:54] Das heißt, du hast mehrere Personen.

[1:07:56] Und jede Person kann wiederum mehrere Kurse belegen.

[1:07:59] Wenn du jetzt diese List of Personen umwandeln willst in eine List of Kurs,

[1:08:04] in der alle Kurse aller Personen drin stehen, dann wird es schwierig.

[1:08:08] Wenn du mit Map hergehst, machst du aus der Liste der Personen eine Liste of.

[1:08:14] Liste of Kurs, weil du zu jeder einzelnen Person mehrere Kurse bekommst.

[1:08:20] Also eine List of Kurs.

[1:08:22] Und dann hast du als Ergebnis eine Liste, in der Listen von Kurse drin stehen.

[1:08:26] Und dieses List of List ist ungünstig.

[1:08:28] Wenn du die Kurse alle in einer einzigen Liste haben willst, dann machst du ein flatMap.

[1:08:33] Und das klopft diese eine Struktur flach.

[1:08:35] Das heißt, anstatt einer Liste aus Listen hast du am Ende eine Liste,

[1:08:39] in der alle Elemente der Listen hintereinander einfach drin stehen.

[1:08:43] Für sowas brauchst du ein flatMap.

[1:08:46] Mit diesen schönen Datenstrukturen, den Funktoren, Monoiden und Monaden,

[1:08:50] kann man herrliche Dinge anstellen.

[1:08:52] Dazu kannst du am besten auch in dieses Buch schauen und es durchlesen.

[1:08:55] Da findest du dann ein paar schöne Beispiele dafür, was man damit machen kann.

[1:08:58] Und ein Beispiel habe ich eben gerade genannt.

[1:09:00] Um Personen zu Kursen umzuwandeln und aus den Kursen dann nochmal die Namen rauszuholen

[1:09:05] und dann die Länge der Namen und so weiter, kannst du so Funktionsketten machen.

[1:09:09] Du machst irgendwie student.flatMap auf getKurse.

[1:09:12] Dann kannst du das wieder mappen auf den Namen des Kurses

[1:09:15] und das kannst du wieder mappen auf die Länge zum Beispiel.

[1:09:18] Und so kannst du dir also verschachtelte Strukturen,

[1:09:21] also verschachtelt nicht, aber hintereinander gekettete Aufrufe von Funktionen zusammenstöpseln.

[1:09:25] Und wenn alle diese Zwischenbestandteile eben Monaden zum Beispiel sind,

[1:09:29] verhalten die sich alle gleich und du kannst überall flatMap und Map und so weiter drauf aufrufen.

[1:09:34] Und so kommst du dann auch in Richtung dieser funktionaleren Programmierung.

[1:09:38] Wahrscheinlich hast du den ersten Anwendungsfeld, wenn du irgendwie Listen verarbeitest.

[1:09:42] Anstatt irgendwie eine ForEach-Schleife drumherum zu machen oder sogar eine Vorschleife mit eigenem Zähler

[1:09:46] und dann da irgendwie was hin und her zu konvertieren und eine neue Liste zu machen

[1:09:49] und da Elemente wieder hinzuzufügen.

[1:09:51] Das ist alles mutable ohne Ende.

[1:09:53] Du brauchst eine neue Liste, in die du Sachen wieder reinfügst.

[1:09:55] Dann wächst die Liste also ständig an.

[1:09:57] Das ist ja die Definition der Veränderbarkeit im Prinzip.

[1:10:00] Und das kannst du mit funktionalen Datenstrukturen alles machen,

[1:10:03] ohne überhaupt selber zu iterieren und selber Listen erzeugen zu müssen.

[1:10:06] Statt also zu sagen, ich habe hier eine Liste von Personen und erzeuge eine neue Liste.

[1:10:11] Iterier jetzt über die Personen und hol mir aus der Person den Namen

[1:10:15] und pack den Namen in diese Liste rein, die ich gerade erzeugt habe.

[1:10:18] Das müsstest du ja klassischerweise machen.

[1:10:20] Könntest du jetzt in der funktionalen Programmierung einfach sagen,

[1:10:22] komm, ListOfStudent.mapStudentGetName und du bist fertig.

[1:10:27] Du musst weder die Iteration programmieren, noch eine eigene Liste bauen

[1:10:30] oder irgendwas hinzufügen.

[1:10:31] Das macht quasi die Sprache automatisch für dich.

[1:10:34] Und da siehst du dran, dass es relativ einfacher,

[1:10:39] es kommt nur noch darauf an, ob man es gut versteht oder nicht,

[1:10:41] auf jeden Fall ist es kürzerer Code

[1:10:43] und du hast halt keine veränderbaren Variablen mehr da drin,

[1:10:46] die du eventuell falsch programmiert hast, nicht gut verstehen kannst, wie auch immer.

[1:10:51] So, kann ich dir nur ans Herz legen, dir das im Detail anzugucken.

[1:10:53] Egal, in welcher Sprache du unterwegs bist,

[1:10:55] die meisten werden heutzutage funktionale Konzepte anbieten

[1:10:57] und ich schreibe dann bei meinen Azubis im Codereview immer dran,

[1:11:00] geht das nicht streamiger, wenn ich sowas sehe wie eine Vorschleife oder eine ForEach-Schleife?

[1:11:05] Denn das kann man auch in Java schon seit Version 8, also inzwischen schon seit 5 Jahren,

[1:11:10] locker machen, indem man Map, FlatMap, Filter, Reduce etc. anwendet.

[1:11:14] Das gibt es auch alles schon seit Jahren in Java tatsächlich.

[1:11:18] Ja, letzter Punkt für heute und den halte ich jetzt auch wirklich kurz,

[1:11:21] da kann ich jetzt nicht mehr im Detail drauf eingehen,

[1:11:23] auf die Asynchronität, die wird im Buch auch hervorragend erklärt.

[1:11:27] Nur kurz nochmal zur Erläuterung, warum ich sowas brauche heutzutage.

[1:11:30] Das Beispiel, was ich vorhin schon mal genannt habe,

[1:11:32] wenn ich eine App habe und starte irgendwie ein Download in der App

[1:11:35] und der Download dauert 5 Minuten, weil das ein riesen Datei ist,

[1:11:37] soll die App in der Zwischenzeit dann nicht mehr reagieren?

[1:11:40] Soll die quasi einfrieren, weil auf den Download gewartet wird?

[1:11:43] Ich glaube, das wollen wir nicht.

[1:11:44] Stattdessen wollen wir, dass der Download im Hintergrund abläuft

[1:11:47] und wenn er fertig ist, soll er mich einfach benachrichtigen, dass ich jetzt weitermachen kann.

[1:11:50] Und genau das ist das Prinzip der Asynchronität.

[1:11:53] Genauso redet man auch von Synchronem und Asynchronem Code.

[1:11:57] Wenn du zum Beispiel eine Methode aufrufst, wenn du die synchron aufrufst,

[1:12:01] dann wartet die aufrufende Methode darauf, dass die aufgerufene Methode fertig wird.

[1:12:06] Und wenn die lange braucht, dann wartet die Methode auch entsprechend lange.

[1:12:10] Wenn du asynchron eine Methode aufrufst, dann macht der Aufrufer einfach so weiter,

[1:12:15] geht in der nächsten Zeile Code weiter und führt einfach weiter aus

[1:12:18] und im Hintergrund wird dann die andere Methode ausgeführt.

[1:12:22] Und erst wenn die fertig ist, dann meldet die sich zurück und sagt,

[1:12:25] so, jetzt bin ich soweit und dann läuft der Code mit dem Ergebnis weiter.

[1:12:29] Aber in der Zwischenzeit, während die erste Methode gewartet hätte bei Synchronaufruf,

[1:12:34] kann sie schon andere Dinge weiter tun.

[1:12:36] Und das ist insbesondere bei Apps oder bei Websites oder wo auch immer interessant,

[1:12:40] denn dann friert die Anwendung halt nicht ein, weil sie wartet,

[1:12:42] sondern du kannst weiter klicken, du kannst mit ihr weiter interagieren

[1:12:45] und irgendwann, wenn der Rückruf quasi da ist, so, jetzt ist der Download fertig,

[1:12:49] dann kann es weitergehen.

[1:12:51] Und das ist der Vorteil der asynchronen Programmierung

[1:12:53] und das wird natürlich gerade in netzwerkbasierten Anwendungen immer wichtiger,

[1:12:57] weil wir da oft Sachen haben, die einfach lange dauern.

[1:12:59] Irgendwas von der Website runterladen oder irgendwas hochladen oder was auch immer.

[1:13:02] Irgendetwas tun, was langsam ist und in der Zeit soll natürlich meine Anwendung weiter reagieren.

[1:13:07] Dafür brauche ich so asynchrone Verarbeitung.

[1:13:09] Das wird im Buch sehr schön erklärt, wie das Ganze funktioniert.

[1:13:12] Da kriegst du dann, wenn du mal überlegst, wie das aussehen müsste,

[1:13:15] dann kannst du von der Methode, die, was auch immer, eine Datei runterlädt,

[1:13:19] nicht eine Datei als Rückgabe wert haben, weil dann würdest du ja darauf warten,

[1:13:22] sondern du hast wieder irgendeinen Container drumherum.

[1:13:25] Am besten eine Monade.

[1:13:27] Und das wäre in Java zum Beispiel das Completable Future.

[1:13:30] Das heißt, du kriegst nicht eine File zurück,

[1:13:33] sondern du kriegst ein Completable Future of File zurück.

[1:13:37] Und dieses Future enthält entweder die Datei, wenn der Download schon fertig ist,

[1:13:41] oder eben nicht, wenn er noch nicht fertig ist.

[1:13:44] Ist nicht das Gleiche wie ein Optional, weil Optional heißt, Wert da oder nicht.

[1:13:47] Completable Future ist, der Wert wird irgendwann einmal da sein.

[1:13:51] Das ist also ein anderes Konzept, von der Idee her aber das Gleiche.

[1:13:54] Du kriegst nicht die Datei selbst zurück, sondern du kriegst diesen Container zurück,

[1:13:58] der die Datei irgendwann einmal enthalten wird.

[1:14:02] Und jetzt die Frage, was machst du dann mit dem Ding?

[1:14:04] Das Ding kannst du dir erstmal in einer Variable zum Beispiel ablegen

[1:14:07] und dann erstmal munter weitermachen mit was auch immer du gerade machen wolltest.

[1:14:10] Und irgendwann musst du dann aber sagen, so jetzt brauche ich aber die Datei.

[1:14:13] Jetzt kann ich nicht mehr weitermachen ohne Datei.

[1:14:15] Und dann kannst du zum Beispiel einem Future sowas machen wie, ich weiß nicht wie es heißt,

[1:14:18] Get vielleicht, und dann gibt er dir die Datei.

[1:14:21] Und wenn sie dann noch nicht runtergeladen ist,

[1:14:23] dann wird auch gewartet, bis sie runtergeladen wurde.

[1:14:25] Das heißt, das wäre dann ein synchroner Aufruf.

[1:14:28] Aber da gibt es dann auch noch verschiedene andere Möglichkeiten,

[1:14:30] wie man damit vernünftig programmiert.

[1:14:32] Hast du vielleicht schon mal gehört, beim Optional ist es auch ein Anti-Pattern,

[1:14:35] sowas zu machen wie, if Optional is present, dann hol den Wert raus.

[1:14:39] Sondern da gibt es auch spezielle Funktionen höherer Ordnung an dem Optional,

[1:14:44] mit dem du eigentlich arbeiten solltest.

[1:14:46] Denn dann, wenn du so mit dem Optional arbeitest,

[1:14:49] if is present, dann irgendwas, dann hast du nichts gewonnen.

[1:14:52] Weil dann könntest du dir das Optional auch sparen und einfach direkt sagen,

[1:14:54] if is null, mach was anderes.

[1:14:57] Und wie du eigentlich damit umgehen solltest, ist genau diese Funktionen zu benutzen.

[1:15:01] Also zum Beispiel Map.

[1:15:03] Statt zu sagen, wenn der Optional gefüllt ist,

[1:15:06] dann nimm den Wert und mach damit etwas,

[1:15:08] könntest du sagen, Optional, Map, Wert auf anderen Wert.

[1:15:12] Und dann entscheidet das Optional, ob es einen Wert hat und dann macht es das Map,

[1:15:16] oder er hat keinen Wert und macht halt nichts,

[1:15:18] beziehungsweise gibt dem der einen Optional zurück.

[1:15:20] Und so arbeitet man eigentlich mit diesen Strukturen.

[1:15:23] Das heißt, wenn du jetzt das CompletableFutureOfFile hast und sagst,

[1:15:26] hey, nicht lad die Datei runter und gib mir die Datei,

[1:15:29] sondern lad die Datei asynchron runter und gib mir ein FutureOfDatei

[1:15:33] und dann direkt in der nächsten Zeile machst,

[1:15:35] if Future, Get oder sonst irgendwas,

[1:15:38] dann hast du nichts gewonnen.

[1:15:39] Weil dann ist es trotzdem ein synchroner Aufruf.

[1:15:41] Stattdessen musst du die angebotenen Funktionen nutzen.

[1:15:44] Und so ein Future hat zum Beispiel eine Funktion andThen.

[1:15:48] Das heißt, du könntest sagen, DateiDownload.andThenSaveFileToDisk oder sowas.

[1:15:56] Und dann macht das Future automatisch, wenn es die Datei runtergeladen hat,

[1:16:00] das Speichern auf die Festplatte.

[1:16:02] Und du musst dich nicht drum kümmern.

[1:16:03] Du musst weder drauf warten, du musst nicht gucken, wann das Ding endlich fertig ist,

[1:16:06] sondern du sagst einfach nur, wenn du irgendwann fertig bist,

[1:16:09] dann mach bitte das und das.

[1:16:11] Und so gibst du dem Future das schon direkt mit

[1:16:13] und musst nicht mal mehr drauf warten, wann es fertig wird,

[1:16:16] weil das Future dann schon weiß, was es tun muss, wenn es fertig ist.

[1:16:19] Und das ist dann wirklich echter asynchroner Code,

[1:16:21] wo du wirklich weiter programmieren kannst und weiter was ausführen kannst,

[1:16:25] obwohl der Download noch gar nicht fertig ist.

[1:16:28] Und wie das Ganze funktioniert und in der API dann aussieht

[1:16:30] und was das für Vor- und Nachteile hat und so,

[1:16:32] das wird alles im Buch sehr intensiv beschrieben.

[1:16:35] Also von daher, guckt da rein.

[1:16:36] Ich habe gesagt, ein ganzes Kapitel nur zum Thema Asynchronität

[1:16:40] und dann auch noch zur konkreten Ausprägung der Reactive Streams.

[1:16:43] Also wirklich sehr, sehr gut da beschrieben.

[1:16:45] Kann ich nur wärmstens ans Herz legen.

[1:16:48] Ja, ich hatte noch einen Punkt auf der Liste.

[1:16:50] Property-Based Testing.

[1:16:51] Das war das Letzte, was im Buch steht,

[1:16:52] aber das hatte ich ganz am Anfang schon erklärt,

[1:16:54] wie du da deine Testfälle etwas anders aufbaust.

[1:16:56] Von daher gehen wir es jetzt mal nicht mehr im Detail durch.

[1:16:58] Ich bin schon viel zu lange heute unterwegs,

[1:17:00] mit eine Stunde zwanzig sehe ich gerade.

[1:17:02] Von daher höre ich an der Stelle hier jetzt einfach mal auf.

[1:17:05] Ich habe jetzt meiner Meinung nach so die wichtigsten Grundlagen

[1:17:08] rund um die funktionale Programmierung erklärt,

[1:17:10] aber auch viele coole, andere wichtige Sachen,

[1:17:12] die auch prüfungsrelevant sind.

[1:17:14] Und zwar, ich wiederhole nochmal, was wir heute besprochen haben,

[1:17:17] Lambda-Ausdrücke, Typ-Inferenz, algebraische Datentypen,

[1:17:21] also Produkttypen und Summentypen, Pattern-Matching,

[1:17:23] Generics und Co- und Kontravarianz.

[1:17:25] Das waren so die allgemeinen Grundlagen,

[1:17:27] die man auch ganz unabhängig von der funktionalen Programmierung

[1:17:30] durchaus in Sprachen wie Java oder C-sharp kennen

[1:17:33] und erklären können sollte als Prüfling.

[1:17:35] Dann haben wir die Prinzipien der funktionalen Programmierung,

[1:17:38] also sowas wie die klassischen Säulen der Objektorientierung.

[1:17:41] Und das wären zumindest die Sachen, die vorher durchgegangen sind.

[1:17:44] Funktionsobjekte, also First Class Citizens,

[1:17:46] dann reine Funktionen, Pure Functions,

[1:17:49] Funktionen höherer Ordnung, das waren die Higher Order Functions,

[1:17:52] Bedarfsauswertung, also Lazy Evaluation,

[1:17:55] Unveränderbarkeit, die Immutability,

[1:17:58] Rekursion als Mittel zur Wiederholung, Recursion,

[1:18:01] und referenzielle Transparenz, Referential Transparency.

[1:18:05] Das sind so die wichtigen Bausteine der funktionalen Programmierung,

[1:18:08] die würde ich von einem 1er-Kandidat auch gerne erläutert haben.

[1:18:12] Und danach haben wir uns noch über funktionale Datenstrukturen ausgetauscht.

[1:18:15] Funktoren, Monoide, Monaden muss man, glaube ich,

[1:18:18] in der Detailtiefe nicht in der mündlichen Prüfung erklären.

[1:18:20] Ich glaube, das geht ein bisschen weit.

[1:18:21] Aber wenn du einfach besser verstehen willst,

[1:18:23] was eigentlich passiert, wenn du da bestimmte Methoden aufrufst,

[1:18:26] wie Map und FlapMap und so weiter, was der Sinn dahinter ist,

[1:18:29] dann schadet es ja nicht, das Ganze hier als Hintergrundwissen zu kennen.

[1:18:35] Und als Letztes, Asynchronität habe ich jetzt etwas kürzer gemacht,

[1:18:38] ist aber für die Praxis natürlich absolut relevant,

[1:18:40] gerade wenn du zum Beispiel Web-Anwendungen entwickelst mit AJAX oder sowas,

[1:18:43] wo du Requests machst, die irgendwie asynchron vom Server zurückkommen.

[1:18:46] Das ist ja gang und gäbe heute.

[1:18:48] Fast jede Web-Oberfläche bietet oder nutzt sowas.

[1:18:51] Von daher solltest du auch verstanden haben, wie das funktioniert.

[1:18:53] Insbesondere, wenn du auch so Frameworks benutzt,

[1:18:55] wie vielleicht React oder Angular oder so,

[1:18:57] da ist ja auch quasi alles automatisch asynchron, was du da machst.

[1:19:01] Und da musst du schon etwas anders programmieren,

[1:19:03] als du es von deinen klassischen Synchron-Methoden-Aufrufen kennst.

[1:19:08] Und deswegen auch hier, Leseempfehlung, guck dir das mal an,

[1:19:11] wie man vernünftig asynchron entwickelt.

[1:19:14] Das kannst du schön in dem Buch nachlesen.

[1:19:16] So, jetzt mach ich aber den Sack zu für heute.

[1:19:19] Ich kann nur sagen, kauf dir das Buch, lies es dir durch.

[1:19:21] Du wirst auf jeden Fall was mitnehmen,

[1:19:23] auch wenn du kein Hardcore-Java-Entwickler bist.

[1:19:25] Und auch für Azubis kann ich es wärmstens empfehlen.

[1:19:28] Ich hoffe, ich konnte dir heute ein bisschen was über die Funktionale Programmierung näher bringen.

[1:19:32] Und hoffe, dass du dir auch das Buch anguckst, weil ich es wirklich empfehlen kann.

[1:19:35] Ich fand es wirklich gut.

[1:19:36] Ich habe noch einige ergänzende Links, zum Beispiel zu meinen Vorträgen

[1:19:39] rund um Themen zur Funktionalen Programmierung,

[1:19:41] aber auch ein paar Artikel, wo noch mal Summen und Produkttypen erklärt werden usw.

[1:19:45] Schau einfach mal in die Shownotes und die findest du wie immer unter

[1:19:48] itberufepodcast.de

[1:19:50] slash 158

[1:19:53] für die 158. Episode, die das hier heute war.

[1:19:57] Und ich würde mich natürlich auch über dein Feedback freuen.

[1:19:59] Schreib mir gerne einen Kommentar oder noch besser hinterlass eine kleine Rezension

[1:20:02] oder auch einfach nur eine kleine Sternebewertung bei Apple Podcasts

[1:20:05] oder wo auch immer du das hier gerade hörst.

[1:20:07] Das würde mich sehr freuen und empfiehle mich weiter, wenn es dir weitergeholfen hat.

[1:20:11] Ja, damit sage ich für heute erstmal vielen Dank für die Aufmerksamkeit,

[1:20:15] die ja heute sehr strapaziert wurde mit einer Stunde zwanzig.

[1:20:18] Aber ich hoffe, es war was Interessantes für dich dabei.

[1:20:20] Und ich sage Tschüss und bis zum nächsten Mal.

Polyglot Clean Code Developer
About the Author
Ausbildungsleiter für Fachinformatiker:innen Anwendungsentwicklung, IHK-Prüfer und ehemaliger Hochschuldozent für Programmierung und Software-Engineering.

2 comments on “Buchclub: Funktionale Programmierung in Java von Herbert Prähofer – IT-Berufe-Podcast #158”

  1. Tang, Jet Loong sagt:

    Ich habe diese Podcast-Folge erst die letzten Tage empfohlen bekommen und habe mir diese gleich auf der Autorückfahrt nach Hause in Spotify angehört.
    Das Thema ist wahrscheinlich gefühlt zeitlos und daher spielte es auch keine Rolle, dass die Folge Nov. 2020 war. Das Buch habe ich gestern gleich bestellt und freue mich bereits darauf. Vielen Dank für die Empfehlung dazu!

    Auch inhaltlich war die Folge vollgespickt mit wichtigen Dingen. Die Kirsche obendrauf war noch die Sprache auf Deutsch.
    Die Folge werte gleichzeitig ich als pädagogisch wertvoll, da es in kurzer Zeit viele Aspekte anschneidet; mit einer notwendigen Tiefe, die wiederum erlaubt ein erstes Verständnis zu bekommen (für manche Konzepte kannte ich den en/de Namen nicht). Die Folge eignet sich meiner Meinung sehr in einem Team von Entwicklern als Einstieg oder Basis eines gemeinsamen gedanklichen Modells. Das ist sehr wertvoll.

    Daher wollte ich auf jeden Fall dieses Feedback dazu geben. Vielen Dank für die Folge und deinen Podcast, den ich allgemein sehr empfehlenswert finde.

  2. Stefan Macke sagt:

    Danke für dein Feedback! Freut mich, wenn ich helfen kann. FP wird immer wichtiger und das genannte Buch finde ich sehr gelungen für einen Einstieg.

Schreibe einen Kommentar

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