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–