Grundkurs funktionale Programmierung mit Scala

Werbung
GRUNDKURS
FUNKTIONALE
PROGRAMMIERUNG
MIT SCALA
lothar PIEPMEYER
Inhaltsverzeichnis
Teil I
1
Abenteuer Lambda . . . . . . . . . . . . . . . . . . . . . . .
1
Mehr denken, weniger tippen . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.1
Sag, was du willst! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2
Der alte Weg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
Abstraktion der Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
1.4
Was ist funktionale Programmierung? . . . . . . . . . . . . . . . . . . . . .
6
1.5
2
Funktionen – Unsere Bausteine . . . . . . . . . . . . . . . . . . . . . . . .
6
1.5.1
Was ist eine Funktion? . . . . . . . . . . . . . . . . . . . . . . . .
6
1.5.2
Was steckt in einer Funktion? . . . . . . . . . . . . . . . . . . . . .
7
1.5.3
Eigenschaften von Funktionen . . . . . . . . . . . . . . . . . . . .
8
1.6
Die referentielle Transparenz . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.7
Das wirkliche Leben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Ein Hauch von Babylon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1
2.2
2.3
Lisp – Die Mutter aller funktionalen Sprachen . . . . . . . . . . . . . . . . 15
2.1.1
Listen – Der Stoff, aus dem Lisp gemacht ist . . . . . . . . . . . . . 16
2.1.2
Operatoren in Lisp . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1.3
Lambda-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.1.4
Mythos Lisp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
ML – Der Pionier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.2.1
Typableitung – Der Compiler erkennt den Typ . . . . . . . . . . . . 22
2.2.2
Muster und Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2.3
Generische Funktionen und Datentypen . . . . . . . . . . . . . . . 24
Haskell – Funktionale Programmierung in Reinform . . . . . . . . . . . . . 25
2.3.1
Haskell – Ein fauler Hund . . . . . . . . . . . . . . . . . . . . . . . 26
2.4
Wann ist eine Sprache funktional? . . . . . . . . . . . . . . . . . . . . . . . 28
2.5
Und die Performance? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
VIII
2.6
Inhaltsverzeichnis
Welche Sprache darf’s denn sein? . . . . . . . . . . . . . . . . . . . . . . . 29
2.6.1
2.6.2
2.7
Teil II
3
4
Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Die funktionale Seite von Java . . . . . . . . . . . . . . . . . 33
Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1
Eine Schleife – was ist das eigentlich? . . . . . . . . . . . . . . . . . . . . 35
3.2
Die Rekursion – das unbekannte Wesen . . . . . . . . . . . . . . . . . . . . 36
3.3
Ist Rekursion praxistauglich? . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.4
Wie werden rekursive Funktionen verarbeitet? . . . . . . . . . . . . . . . . 38
3.5
Endrekursion, die schnelle Rekursion . . . . . . . . . . . . . . . . . . . . . 40
3.6
Eine einfache Formel und ihre schwerwiegenden Folgen . . . . . . . . . . . 43
3.7
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Alles bleibt, wie es ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.1
Konsistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.2
Nichts ist beständiger als der Wandel . . . . . . . . . . . . . . . . . . . . . 50
4.3
4.4
Versteckte Daten sind gute Daten . . . . . . . . . . . . . . . . . . . . . . . 51
Invarianten – darauf ist Verlass . . . . . . . . . . . . . . . . . . . . . . . . 52
4.5
Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.6
Die Methode equals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.7
Sichere Ergebnisse mit defensiven Kopien . . . . . . . . . . . . . . . . . . 55
4.8
Konkurrierende Zugriffe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.9
Geänderte Hash-Codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.10 Unveränderbare Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.11 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.12 Unveränderbare Klassen in der Java-API . . . . . . . . . . . . . . . . . . . 61
4.13 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
5
Funktionen höherer Ordnung . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5.1
Arrays sortieren leicht gemacht . . . . . . . . . . . . . . . . . . . . . . . . 63
5.2
Eine flexiblere Lösung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
5.3
Funktionen als Ergebnisse von Funktionen . . . . . . . . . . . . . . . . . . 68
5.4
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Inhaltsverzeichnis
6
7
Unveränderbare Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.1
6.2
Arrays raus! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Verkettete Listen – Vom Nobody zum Star . . . . . . . . . . . . . . . . . . 74
6.3
Nützliche Methoden für die Arbeit mit Listen . . . . . . . . . . . . . . . . . 76
6.4
Das schwer erreichbare Listenende . . . . . . . . . . . . . . . . . . . . . . 77
6.5
Die Faltung – eine universelle Funktion . . . . . . . . . . . . . . . . . . . . 78
6.6
Eine Leihgabe aus der imperativen Programmierung . . . . . . . . . . . . . 81
6.7
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Anfragen an Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
7.1
Auswahlen aus Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
7.2
Listenelemente abbilden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
7.3
Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
7.4
Primzahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.5
Verknüpfungen von Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.6
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Teil III
8
9
IX
Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Die Scala-Entwicklungsumgebung . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.1
Ohne Java geht nichts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
8.2
8.3
Installation und erste Schritte . . . . . . . . . . . . . . . . . . . . . . . . . 98
Scala-Skripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
8.4
Eigene Typen mit Scala-Klassen . . . . . . . . . . . . . . . . . . . . . . . . 99
8.5
Noch ein Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
8.6
Der Scala-Bazar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
8.7
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Ausdrücke in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.1
Erste Eindrücke mit einfachen Ausdrücken . . . . . . . . . . . . . . . . . . 105
9.2
Konstante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
9.3
Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.4
Alle kennen Predef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.5
Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
9.6
Importe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
9.7
Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
X
Inhaltsverzeichnis
10 Scala-Typsystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.1 In Scala gibt es keine primitiven Typen . . . . . . . . . . . . . . . . . . . . 113
10.2 Alles ist ein Objekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
10.3 Objekte vergleichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
10.4 Werte- und Referenztypen . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
10.5 Literale und Typumwandlungen . . . . . . . . . . . . . . . . . . . . . . . . 116
10.6 Der Typ Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
10.7 Der Typ Null . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
10.8 Der Typ Nothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
10.9 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11 Methoden in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.1 Jede Methode hat einen Typ . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.2 Generische Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
11.3 Konstante als Grenzfall von Methoden . . . . . . . . . . . . . . . . . . . . 122
11.4 Was steht in einer Methode? . . . . . . . . . . . . . . . . . . . . . . . . . . 123
11.5 Methoden ohne Rückgabewert . . . . . . . . . . . . . . . . . . . . . . . . . 125
11.6 Methoden in Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
11.7 Methoden für beliebig viele Argumente . . . . . . . . . . . . . . . . . . . . 126
11.8 Endrekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
11.9 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
12 Funktionen in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
12.1 Die Definition einer Funktion . . . . . . . . . . . . . . . . . . . . . . . . . 131
12.2 Funktionen sind auch Objekte . . . . . . . . . . . . . . . . . . . . . . . . . 133
12.3 Die partielle Anwendung einer Funktion . . . . . . . . . . . . . . . . . . . 134
12.4 Eine scharfe Sache: Das Curry-Prinzip . . . . . . . . . . . . . . . . . . . . 136
12.5 Funktionen höherer Ordnung . . . . . . . . . . . . . . . . . . . . . . . . . 138
12.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
13 Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
13.1 Eigenschaften von Tupeln . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
13.2 Die Tupel-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
13.3 Mustererkennung für Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . 146
13.4 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Inhaltsverzeichnis
XI
14 Klassen und Vererbung in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . 149
14.1 Klassendefinitionen in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . 149
14.1.1 Ein alter Bekannter zum Einstieg . . . . . . . . . . . . . . . . . . . 149
14.1.2 Konstruktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
14.1.3 Attribute und parameterfreie Methoden . . . . . . . . . . . . . . . . 152
14.1.4 Mehrere Fliegen mit einer Klappe schlagen . . . . . . . . . . . . . . 153
14.1.5 Was bedeutet „rechtsassoziativ“? . . . . . . . . . . . . . . . . . . . 154
14.2 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
14.2.1 Methoden überschreiben . . . . . . . . . . . . . . . . . . . . . . . 154
14.2.2 Konstruktorverkettung . . . . . . . . . . . . . . . . . . . . . . . . . 156
14.2.3 Polymorphie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
14.2.4 Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
14.3 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
15 Singletons: Objekte können einsam sein . . . . . . . . . . . . . . . . . . . . . . 161
15.1 Es kann nur einen geben . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
15.2 Statische Mitglieder waren gestern . . . . . . . . . . . . . . . . . . . . . . 163
15.3 Begleiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
15.4 Singletons zur Objektverwaltung . . . . . . . . . . . . . . . . . . . . . . . 165
15.5 Singletons importieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
15.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
16 Mustererkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
16.1 Muster in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
16.2 Einfache Muster in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
16.3 Muster für Tupel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
16.4 Welche Muster gibt es? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
16.5 Muster für Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
16.6 Muster in Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
16.7 Partielle Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
16.8 Exceptions und Mustererkennung . . . . . . . . . . . . . . . . . . . . . . . 175
16.9 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
17 Extraktoren und Case-Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
17.1 Besser als null: Der Typ Option . . . . . . . . . . . . . . . . . . . . . . 178
17.2 Extraktormuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
17.3 Optionale Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
17.4 Extraktoren sind universelle Inspektoren . . . . . . . . . . . . . . . . . . . 180
XII
Inhaltsverzeichnis
17.5 Lesbarer Code mit apply . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
17.6 Variable Extraktoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
17.7 Alles frei Haus mit Case-Typen . . . . . . . . . . . . . . . . . . . . . . . . 183
17.8 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
18 Listen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
18.1 Die leere Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189
18.2 Listen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
18.3 Einfache Methoden für Listen . . . . . . . . . . . . . . . . . . . . . . . . . 190
18.4 Listenmuster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
18.5 Weitere einfache Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . 192
18.6 Mengenoperationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
18.7 Das Begleitobjekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
18.8 Methoden höherer Ordnung für Listen . . . . . . . . . . . . . . . . . . . . 196
18.8.1 Beispiel: Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . 197
18.8.2 Die Faltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
18.9 Das Springerproblem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
18.10 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
19 Scala kann auch faul sein . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
19.1 Die Initialisierung kann warten . . . . . . . . . . . . . . . . . . . . . . . . 203
19.2 Faule Parameter mit Call-By-Name . . . . . . . . . . . . . . . . . . . . . . 205
19.3 Streams: Daten bei Bedarf . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
19.4 Unendliche Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
19.5 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
20 Es müssen nicht immer Listen sein . . . . . . . . . . . . . . . . . . . . . . . . . 211
20.1 Mengen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
20.2 Der Typ Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
20.3 Der Typ Map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
20.4 Collections in anderen Geschmacksrichtungen . . . . . . . . . . . . . . . . 218
20.5 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
21 Fast wie zu Hause: for-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . 221
21.1 Eine nicht ganz so funktionale Methode höherer Ordnung . . . . . . . . . . 221
21.2 Komplexere for-Ausdrücke . . . . . . . . . . . . . . . . . . . . . . . . . 222
21.3 for-Ausdrücke mit filter und map . . . . . . . . . . . . . . . . . . . . 222
21.4 Mehr Übersicht mit flatMap . . . . . . . . . . . . . . . . . . . . . . . . 223
Inhaltsverzeichnis
XIII
21.5 for-Ausdrücke sind keine Schleifen . . . . . . . . . . . . . . . . . . . . . 224
21.6 for-Ausdrücke für eigene Typen . . . . . . . . . . . . . . . . . . . . . . . 225
21.7 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
Teil IV
Scala kann mehr . . . . . . . . . . . . . . . . . . . . . . . . 227
22 Veränderbare Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
22.1 Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
22.2 Veränderbare Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
22.3 Die Rückkehr der Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
22.4 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
23 Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
23.1 Traits und Java-Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
23.2 Konkrete Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
23.3 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
23.4 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
24 Varianz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
24.1 Kovarianz von Java-Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . 241
24.2 Kovarianz von generischen Java-Typen . . . . . . . . . . . . . . . . . . . . 242
24.3 Mehr Kovarianz in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
24.4 Kontravarianz in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
24.5 Varianz in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
25 Pakete und Sichtbarkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
25.1 Pakete in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
25.2 Sichtbarkeit in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
25.3 Privater als privat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
26 Typumwandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
26.1 Implizite Methoden für implizite Casts . . . . . . . . . . . . . . . . . . . . 255
26.2 Wozu noch explizite Casts? . . . . . . . . . . . . . . . . . . . . . . . . . . 257
26.3 Angereicherte Typen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
26.4 Pimp Your Library! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
26.5 Sprachfeatures mit impliziten Casts umsetzen . . . . . . . . . . . . . . . . . 259
26.6 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
XIV
Inhaltsverzeichnis
27 Parallele Programmierung mit Aktoren . . . . . . . . . . . . . . . . . . . . . . 261
27.1 Viele Köche verderben den Brei . . . . . . . . . . . . . . . . . . . . . . . . 262
27.2 Parallele Programmierung mal anders . . . . . . . . . . . . . . . . . . . . . 262
27.3 Erste Versuche mit Aktoren . . . . . . . . . . . . . . . . . . . . . . . . . . 264
27.4 Der Typ Actor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
27.5 Die Actor-Fabrik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
27.6 Einfache Standardprobleme und ihre Lösung . . . . . . . . . . . . . . . . . 267
27.7 Aktoren können antworten . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
27.8 Producer-Consumer-Probleme . . . . . . . . . . . . . . . . . . . . . . . . . 269
27.9 Blockierende Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
27.10 Deadlocks – Nichts geht mehr . . . . . . . . . . . . . . . . . . . . . . . . . 272
27.11 Aufgaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
Kapitel 2
Ein Hauch von Babylon
Wir werden jetzt am Beispiel der drei Programmiersprachen Lisp, ML und Haskell erfahren,
wie die funktionalen Konzepte aus Kapitel 1 in der Praxis umgesetzt werden. Die Sprachen
erlernen wir hier natürlich nicht in vollem Umfang; wir erarbeiten uns einen ersten Eindruck.
Wenn man bei dieser Übersicht nicht gleich alles versteht, ist das nicht dramatisch: Da alle
wichtigen Themen noch in eigenen Kapiteln ausführlich erläutert werden, kann man dieses
Übersichtskapitel bei der ersten Lektüre des Buches auslassen.
2.1
Lisp – Die Mutter aller funktionalen Sprachen
Die Geschichte der höheren Programmiersprachen fing zwar in den 1950er-Jahren mit der
imperativen Sprache Fortran an, doch bereits kurze Zeit später entwickelte John McCarthy
die Programmiersprache Lisp (List Processing). Die Syntax von Lisp hat Ähnlichkeit mit
dem Lambda-Kalkül und besticht durch den Charme ihrer Schlichtheit sowie die Vielfalt
ihrer Möglichkeiten. Um Lisp zu beschreiben, brauchen wir nur die beiden Begriffe Atom
und S-Ausdruck:
Ein Atom ist ein Symbol oder ein Literal etwa in Form einer ganzen Zahl, einer Fließkommazahl oder eines Textes. Beispiele für Atome sind Zahlen wie 47.11 und 23 oder der Text
"Hello Lisp". Je nach Lisp-Dialekt1 können weitere Arten von Literalen, wie etwa Brüche, hinzukommen. Lisp verfügt über einen Satz vordefinierter Symbole wie +, list oder
defun, die etwa die gleiche Bedeutung wie Schlüsselworte in anderen Programmiersprachen
haben. In Lisp können Entwickler aber auch eigene Symbole vereinbaren.
Das zweite wichtige Konzept, der S-Ausdruck, ist, wie so vieles in der funktionalen Programmierung, rekursiv definiert. Ein S-Ausdruck ist entweder
ein Atom oder
ein Paar (U . V), wobei U und V ebenfalls S-Ausdrücke sind.
1
Es gibt so viele Dialekte, dass Lisp oft als Sprachfamilie und nicht als Sprache bezeichnet wird.
16
2 Ein Hauch von Babylon
Auch wenn wir Ausdrücke der Form (U . V) als S-Ausdrücke bezeichnen, sind sie in
dieser Form keine syntaktisch korrekten Ausdrücke. In Lisp notieren wir S-Ausdrücke wie
folgt:2
(cons 47 11)
Dieser Lisp-Ausdruck konstruiert (daher der Name cons für das Symbol) den S-Ausdruck
(47 . 11)
2.1.1
Listen – Der Stoff, aus dem Lisp gemacht ist
Am Beispiel von Listen machen wir uns jetzt klar, dass wir S-Ausdrücke wie russische
Matroschka-Puppen verschachteln und so komplexe Datenstrukturen definieren können. Da
wir mit cons zwei S-Ausdrücke zu einem neuen S-Ausdruck zusammenfassen, konstruieren
wir Listen, indem wir den Operator cons wiederholt anwenden:
(cons 23 (cons 47 11))
Der entsprechende S-Ausdruck
(23 . (47 . 11))
ist eine rekursive Beschreibung der Liste
(23
47
11)
Vor diesem Hintergrund können wir S-Ausdrücke auch anders charakterisieren. Ein SAusdruck ist
ein Atom oder
eine Liste.
Listen spielen eine zentrale Rolle in Lisp; wir müssen sie daher bequem vereinbaren und
verarbeiten können.
Das Symbol nil bezeichnet die leere Liste, wir verwenden es auch als Markierung für das
Ende einer Liste. Wenn wir eine Liste von links nach rechts durchlaufen, erkennen wir an der
Markierung, wann wir das Listenende erreicht haben. Die Liste (23 47 11) definieren wir
also so:
(cons 23
(cons 47
(cons 11
nil )))
Auch wenn wir Zeilenumbrüche zur Strukturierung einsetzen, bleibt das für eine so kleine
Liste viel zu viel Schreibarbeit. Wenn wir unsere Listen immer so aufschreiben müssten,
würden wir vielleicht die Meinung einiger Leute über Lisp teilen, die behaupten, Lisp sei „the
2
Die Beispiele wurden mit dem Werkzeug LispWorks entwickelt, das es in einer kostenlosen Personal-Edition gibt.
Der verwendete Lisp-Dialekt ist Common Lisp.
2.1 Lisp – Die Mutter aller funktionalen Sprachen
17
most intelligent way to misuse a computer“.3 Einer der vielen Standardoperatoren erleichtert
uns das Leben. Anstatt viele cons-Operationen zu verschachteln, können wir viel bequemer
(list 23 47 11)
schreiben. Wie können wir jetzt auf die Daten zugreifen, die in Listen stehen?
Da bis auf die leere Liste jede Liste ein S-Ausdruck von der Form (U . V) ist, bestehen
nichtleere Listen aus dem Listenanfang U und dem Listenende V. In einer einfachen Liste,
wie in der aus unserem Beispiel, ist U ein Atom in Form einer Zahl. Es ist aber auch möglich,
dass U selbst eine Liste ist. So können wir auch mit Datenstrukturen wie Matrizen arbeiten:
(list
(list 11 12)
(list 21 22)
)
Egal, wie der S-Ausdruck aussieht, der zu der Liste gehört, mit Hilfe der Operatoren car
und cdr kommen wir an seine Anfangs- und an seine Restkomponente. Wir probieren das
einmal an unserem Beispiel aus. Die Ausdrücke
(car (list 23 47 11))
und
(cdr (list 23 47 11))
liefern die Werte 23 und (47 11). Der Wert des Ausdrucks
(car (cdr (list 23 47 11)))
ist 47.
2.1.2
Operatoren in Lisp
Einige der vordefinierten Operatoren4 , die es in Lisp gibt, haben wir bereits kennengelernt.
Hier zwei weitere Beispiele, die wir gleich benötigen:
(+ 47 11) liefert 58, also die Summe aus 47 und 11.
(eq 47 11) prüft, ob die beiden Zahlen übereinstimmen und liefert nil, da sie verschieden sind. Dagegen liefert (eq 23 23) den Wert T.
Die aus Java bekannten Booleschen Werte true und false gibt es in Lisp nicht; ihnen entsprechen also T und nil. Alte Bekannte wie if finden wir in Lisp in Form eines Operators:
3
4
Das Zitat ist etwas aus dem Zusammenhang gerissen. Es stammt von E. Dijkstra [Dij72] und hat in seinem ursprünglichen Kontext eine andere Bedeutung: „LISP has been jokingly described as „the most intelligent way to
misuse a computer“. I think that description a great compliment because it transmits the full flavor of liberation: it
has assisted a number of our most gifted fellow humans in thinking previously impossible thoughts.“
Genau genommen unterscheidet man in Lisp zwischen Makros und Funktionen. Für unsere kurze Einführung in
Lisp fassen wir beide Begriffe zu dem Begriff „Operator“ zusammen.
18
2 Ein Hauch von Babylon
(if "something" "true" "false") liefert den Text "true"
(if nil "true" "false") liefert den Text "false"
Der if-Operator ist das erste Element einer Liste mit vier Elementen. Wenn das zweite Element dieser Liste den Wert nil hat, so ist das Ergebnis des Ausdrucks gleich dem Wert des
vierten, sonst gleich dem Wert des dritten Parameters. Wenn wir den if-Operator anwenden, erhalten wir also ein Ergebnis. Daher entspricht dieser Operator in Java weniger der
if-Anweisung als vielmehr dem Bedingungsoperator, der – in Java – wie folgt angewendet
wird:
String s= (array.length==0 ? "no" : "some")+" arguments"
Selbstverständlich können wir auch eigene Funktionen implementieren:
(defun sum(v)
(if (<= v 0)
0
(+ v (sum (- v 1)))))
Die rekursive Funktion sum hat den Parameter v und berechnet die Summe der ganzen Zahlen von 0 bis v. Wenn v einen Wert kleiner oder gleich 0 hat, ist diese Summe gleich 0,
ansonsten ist das Ergebnis gleich der Summe aus v und dem Wert von sum(v-1). Da wir
schon alte Lisp-Hasen sind, können wir unsere neue Funktion auch gleich verwenden und
sehen, dass etwa (sum 3) den Wert 6 hat.
In der folgenden Funktion addieren wir alle Elemente einer Liste:
(defun sum(xs)
(if (eq (cdr xs) nil)
(car xs)
(+ (car xs) (sum (cdr xs)))))
Das ist zwar ein opulentes Klammergebirge, mit etwas Übung sieht man aber schnell, was
hier passiert: Wenn das Ende der Liste (cdr xs) den Wert nil hat, haben wir es mit einer
einelementigen Liste zu tun. Wir können dann nil ignorieren und das Ergebnis (car xs)
direkt angeben.
Wenn unsere Liste mehr als ein Element enthält, besteht das Ergebnis aus der Summe
des ersten Listenelements und
der Summe der restlichen Listenelemente.
Das Ergebnis von (sum (List 23 47 11)) ist beispielsweise 81.
2.1.3
Lambda-Ausdrücke
Mit einer einzigen Änderung können wir aus sum eine Funktion machen, die die Zahlen einer
Liste miteinander multipliziert. Ganz allgemein könnten wir die Addition + durch andere
Funktionen ersetzen und so das Verhalten von sum ändern:
2.1 Lisp – Die Mutter aller funktionalen Sprachen
19
Wie wäre es, wenn wir Operatoren wie + oder *, die wir auf allen Elementen der Liste
ausführen wollen, einfach als Argument übergeben könnten?
Der Operator würde die Listenelemente zu einem atomaren Wert akkumulieren; die Summenbildung ist dann nur ein Spezialfall.
Besonders elegant wäre es, wenn wir Operatoren wie alle andere Werte behandeln könnten. Derzeit vereinbaren wir Funktionen in Lisp so ähnlich wie in anderen Sprachen: Unsere
Funktionen haben einen Namen, Parameter und einen Rumpf. Ein wesentlicher Unterschied
etwa zu ganzzahligen Ausdrücken besteht in der Benennung von Funktionen: Zahlen haben
keine Namen. In Lisp können wir Funktionen anonym definieren, also ohne ihnen explizit
einen Namen zu geben:
(lambda(a b) (+ a b))
An der Namensgebung dieses Operators sehen wir, dass Lisp sich ganz bewusst am LambdaKalkül (siehe Kasten „Der Lambda Kalkül“ auf Seite 10) orientiert. Lambda-Funktionen führen wir mit dem Operator funcall aus:
(funcall (lambda(a b) (+ a b)) 47 11 )
Auch wenn das richtige Ergebnis 58 rauskommt, erscheint es uns zunächst sinnlos (anonyme)
Lambda-Funktionen auszuführen: Genauso gut hätten wir
(+ 47 11)
schreiben können. Wozu also der ganze Aufwand? Der Witz ist, dass wir Lambda-Ausdrücke
anderen Funktionen als Parameter übergeben und sie dann auch
in anderen Funktionen ausführen können.
Das ist aber genau das, was wir brauchen, um unsere Funktion sum zu verallgemeinern.
Der allgemeinen Funktion accu übergeben wir neben der Liste xs eine Funktion als ersten
Parameter:
(defun accu(fn xs)
(if (eq (cdr xs) nil)
(car xs)
(funcall fn (car xs) (accu fn (cdr xs)))))
Die einzige Zeile im Funktionsrumpf, die wir im Vergleich zur Funktion sum geändert haben,
enthält den Aufruf der Funktion fn, die wir ja als Argument übergeben haben und auch bei
den rekursiven Aufrufen weiterreichen. Mit Hilfe unserer neuen Funktion accu ermitteln
wir das Produkt über die Elemente einer Liste, indem wir außer der Liste noch die LambdaFunktion (* a b) übergeben:
(accu (lambda(a b) (* a b)) (List 23 47 11))
Stichwortverzeichnis
++ 194
:: 190
::: 193
<: 246
== 115
=> 132, 169
>: 246
* 213
- - 194–195
/: 198–199
:/ 198–199
Abstrakte Klassen 158–159
Ackermann-Funktion 40, 47
act 265
Actor 265–267
Aktoren 30, 262–275
Algorithmus 10
Anweisungen 8
Any 115, 155
AnyRef 116, 117
AnyVal 116, 186
Application 235
apply 133, 181–183, 190, 193, 214, 217
Argumente 6
Array 73–74, 232, 272
ArrowAssoc 259
asInstanceOf 156
assert 109
Attribute 149, 152–153
Backtracking 202
Bedingungsoperator 9
Begleitklasse 163–164
Begleitobjekt 163–164
Berechenbarkeit 10
binäre Suchbäume 212
Boolean 116
Bottom-Types 118
Byte 116
Call-By-Name 205–206
Caml 25
case 183–186
Case-Klasse 183–184
Case-Singleton 185
Case-Typ 183–187
Cast 116–117, 255–260
Cast-Operator 256
catch 175
Char 116
collection.immutable 212
collection.mutable 212
cons 190
contains 214, 258
count 196–197
curried 137
Curry-Form 136, 137
Curry-Prinzip 25, 136–138, 205
Deadlocks 261, 272–275
Defensive Kopien 55–57
deklarativ 4
Dekorierer 238
Delegation 258
Double 116
DRY 52, 78, 134
Eclipse 97
Endrekursion
40–43, 81, 127–128
280
Entschlüsselung 68–70
Entwicklungsumgebung 97
Entwicklungswerkzeuge
fsc 100–101
sbaz 101–102
scalac 100
scala 98, 100
Dokumentation Scala-API 101
Entwurfsmuster 65–68, 161, 238
eq 115, 163
equals 55, 115, 172
Exception 175–177
Checked 175
exists 196–197, 214
extends 236
Extraktor 178–187, 192
variabler 183
Factory 163, 182, 190, 266
Faltung 46, 78–82, 198–199
effiziente Implementierung 80, 81
linke 80
rechte 81
faule Initialisierung 203–205
Fibonacci-Zahlen 43–45, 209
Formel von de Moivre 44
Größenordnung 44
filter 86–88, 196–197, 199, 201, 217,
222–223
flatMap 196–197, 223–224
Float 116
foldLeft 198–199
foldRight 198–199
for-Ausdrücke 221–226, 270
forall 196–197
foreach 221, 222
Frame 38, 39
fromFile 203–204
fromURL 203–204
Funktion 6–10, 131–141
anonyme 28, 132
höherer Ordnung 24, 63–72, 138–140
partielle 174–175, 178
totale 174
und Objekte 133–134
Stichwortverzeichnis
Funktionale Programmierung und Veränderbarkeit 81
Generische Typen
Google 85
Guard 174, 202
241
Hash-Tabellen 58–59, 212
Haskell 25–28, 203
head 190, 191
if 109
imperative Programmierung
implicit 256
implizite Casts 258–259
import 110
Infix-Notation 110
Inkonsistenz 261
Inspektor 51
Int 115
intersect 194–195
Invarianten 52–53, 55–57
Invarianz 245
isInstanceOf 155
5
Java
anonyme Klassen 67, 79
Comparable 64
Generics 24
Generische Arrays 243
Interfaces 236
Kovarianz von Rückgabetypen 243
Muster 167–169
switch 168
Typeinschränkungen 244
Unveränderbare Listen 73–83
Java-Bytecode 40–41
java.util.concurrent 262
JavaScript 28
Kapselung 51–52
Key-Value-Paare 215, 217, 259
keySet 216
keysIterator 216
Klassen 99–100, 149–154
Komplexität 38
Konsistenz 49–52
Stichwortverzeichnis
Konstante 106
Konstruktoren 150–152
Konstruktorverkettung 156
Kontravarianz 244–245
Kovarianz 241–245
kritischer Abschnitt 262
Lambda-Ausdruck 9, 10
Lambda-Kalkül 9–10, 19
Laziness 26, 27, 203–210
lazy 204
Lebendigkeit 261
length 191
LinearSequenceLike 221
Linksassoziativität 154
Lisp 15–20, 28, 75
car-Operator 17
cdr-Operator 17
cons 16
anonyme Funktionen 19
Atom 15
Datentypen 20
Funktion 18
Lambda-Ausdruck 18
Listen 16
Paar 15
S-Ausdruck 15, 16
Symbol 15
List 189–202
Listen 189–202
Anfügen am Ende 77
Begleitobjekt 195–196
Endstück 75
Factory 190
leere 189–190
Operationen 76
Ressourcenbedarf 78
Suche in 211
Listenkopf 75
Long 116
loop 271, 273
magic value 177
Mailbox 264, 268
main 149
map 85, 87–88, 196–197, 201, 206, 222–
223
281
Map 214–218
MapReduce 85
match 169–170
Mehrfachvererbung 238–239
Mengen 211–214
Operationen 213
Suche in 213
Methode 121–129
Überschreiben von 154–156
Generische 122
implizite 255–257
Lokale 125–126
mit leerer Parameterliste 123, 152
mit variabler Parameterliste 126–127
ohne Parameterliste 123, 152
ohne Rückgabewert 125
mkString 192
ML 21–25, 28, 135, 177
anonyme Funktion 22
Funktion 22
Generische Funktionen 24–25
Lambda-Ausdruck 26
Listen 24
Multiparadigmensprache 25
Muster 22, 167
Extraktoren 171, 178–179
in Funktionen 174
Konstante 171
Listen 171, 191–192
Platzhalter 171
Tupel 171
Typen 172–174
Variable 171
Mustererkennung 22, 26, 135, 146–147,
167–177, 268
Mutator 51
Nachrichten 264
asynchroner Versand 268
synchroner Empfang 268
synchroner Versand 269
Nebenwirkungen 11
NetBeans 97
None 178–180
Nothing 117–118, 184, 189
notify 262
282
null
Null
Stichwortverzeichnis
178
117
object 162, 163
Object-Sharing 262
objektprivat 252, 272
OCaml 25
Option 178–180
OSGi 97
override 155
Pakete 249–250
parallele Programmierung 11, 57–58, 261–
275
Parameter 6
freie 134
gebundene 134
PartialFunction 175, 264
partielle Anwendung 134–136, 138, 139
Performance 28
pimp my library 258
Pivot 89–90, 197
Platzhalter 110, 132, 169
Polymorphie 24, 156–158
pop 38
Prädikat 86
Predef 108–109, 165, 213, 215, 255, 257
Primzahl 90–91, 199
Prinzip des einheitlichen Zugriffs 152–153,
158
Problem der speisenden Philosophen 272
Producer-Consumer-Probleme 269–272
push 38
Python 28
Quicksort 40, 89–90, 197–198
Komplexität 89
Rückgabetyp 7
range 195
Range 257
react 270, 272
readInt 108
receive 264, 265, 268, 272
Rechtsassoziativität 153–154
reduceLeft 198–199
reduceRight 198–199
Redundanzen 52
Referentielle Transparenz 11–12
Referenztypen 116
Registry 165
reguläre Ausdrücke 167
rein funktionale Programmierung
30
Rekursion 35–47
Abbruchkriterium 36
Java-Compiler 42–43
kaskadierende 44
lineare 44
Selbstaufruf 36
rekursive Funktionen 9
relationale Datenbanken 3, 214
removeDuplicates 194
REPL 98
reply 268
require 153, 231
return 123
reverse 255
RichInt 257
Scala-Shell 98
Scala-Skripts 98–99
scala.actors 263
scalac 97
Scheduler 275
Schleife 9, 35–36
sealed 186, 258
Selektoren 145
self 264, 266
Semikolon 124
Seq 126
SequenceLike 194–195
Set 213–214, 232
Short 116
Sicherheit 261
Sichtbarkeit 150, 250–252
private 250
protected 250
öffentliche 250
objektprivate 252
Sieb des Eratosthenes 90–91, 199
Singleton 99, 161–166, 181
Import 165–166
12, 27,
Stichwortverzeichnis
size 191
Some 178–180
Sortieren 63
sortWith 198
Springerproblem 199–202, 211, 224
SQL 3, 5, 46, 85, 86
Stack 38, 39, 76
blockierender 271–272
Standardkonstruktor 150, 151
privater 152
Standardwerte 230
start 266
Starvation 261
static 161, 163
Strategiemuster 63–68, 91, 132
Stream 206–210
unendlicher 27, 208–209
Stream.cons 207
synchronized 262
Tabelle 214
tail 190, 191
tailrec 128
Thread 68
Threadpool 263
trait 235
Trait 235–240
Attribut 236
Konkrete Methode 236–238
TraversableLike 214, 255
TreeSet 218
try 175
Tupel 143–148, 200
Tupelmuster 146–147, 170–171
Turing-Maschinen 9–10
Turing-Vollständigkeit 10
Typ 7
Typableitung 21, 22, 113
type 108
Type-Erasure 173
Typeinschränkungen 246
Typhierarchie 238
Typparameter 24
Typsystem 21, 113–119
dynamisches 21
explizites 21
implizites 21, 113
Scala 113
283
sicheres 21, 113
statisches 21, 113
unsicheres 21
Typumwandlung 255–260
mehrdeutige 256
und Vererbung 258
unapply 180, 181, 183
for-Ausdrücke
und imperative Programmierung
225
union 194–195
Unit 116–117, 202
Unterpakete 251
Unveränderbarkeit
Performance 60–61
match
unvollständig 186
update 183, 232
updated 215
valuesIterator 216
var 229
Variable 108, 229
Varianz 241–247
veränderbare Attribute 229–232
Inspektor 230, 231
Mutator 230, 231
veränderbare Daten 229–233
Veränderbarkeit 56
Vererbung 154–159
Verknüpfung von Funktionen 7
Verschlüsselung 68–70
von-Neumann-Architektur 4–5, 37
Vorbedingungen 153
wait 262
Webseiten lesen 175
Wertetypen 116, 186
while 109, 221, 229
with 236, 237
WrappedString 138, 255, 257
yield 223–225, 274
zip 91–92, 195
zipWithIndex 195
Zustand 49, 50
Zustandsänderungen 50
224–
Herunterladen