Sie sind auf Seite 1von 204

Objekt-Funktionale Programmierung am Beispiel von Scala

Objekt-Funktionale Programmierung am Beispiel von

SCALA

Thorsten Jolitz 1. Auage 2012

Impressum Copyright: Auage: Layout und Satz: Druck und Verlag:

c 2012 Thorsten Jolitz 1. Auage 2012 Thorsten Jolitz epubli GmbH, Berlin, www.epubli.de

Wenn mehrere Wahrheiten einleuchtend sind und sich unbedingt widersprechen, bleibt dir nichts anderes brig, als deine Sprache zu wechseln. - Antoine de Saint-Exupry, Die Stadt in der Wste

Danksagung Diese Buch ist aus einer Abschluarbeit im Studiengang Master of Computer Science an der FernUniversitt in Hagen hervorgegangen. Ich danke Prof. Steimann fr die Mglichkeit diese Arbeit an seinem Lehrstuhl Programmiersysteme schreiben zu knnen, und Fr. Dr. Daniela Keller fr die hervorragende Betreuung in allen Phasen der Arbeit. Alle verbleibenden Fehler sind selbstverstndlich meine eigenen.

Inhaltsverzeichnis
1. Einleitung 1

I.

Ausgangspunkt Imperative Programmierung


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3
5 5 5 5 6 7 8 9 9 10 12 12 13 14 15 19 19 20 20 20 21 22

2. Elemente der Imperativen Programmierung 2.1. Entstehung und Erfolgsfaktoren . . . . . 2.2. Variablen . . . . . . . . . . . . . . . . . 2.2.1. Eigenschaften von Variablen . . . 2.2.2. Name . . . . . . . . . . . . . . . 2.2.3. Adresse und Wert . . . . . . . . 2.2.4. Typ . . . . . . . . . . . . . . . . 2.2.5. Lebenszeit . . . . . . . . . . . . . 2.2.6. Sichtbarkeit . . . . . . . . . . . . 2.3. Zuweisungsoperation . . . . . . . . . . . 2.4. Kontrollanweisungen . . . . . . . . . . . 2.4.1. Steuerung des Kontrollusses . . 2.4.2. Sequenz . . . . . . . . . . . . . . 2.4.3. Auswahl . . . . . . . . . . . . . . 2.4.4. Iteration . . . . . . . . . . . . . .

3. Abstraktion und Strukturierung 3.1. Abstraktion in der Programmierung . . . . . . . 3.2. Prozeabstraktion . . . . . . . . . . . . . . . . . 3.3. Datenabstraktion . . . . . . . . . . . . . . . . . . 3.3.1. Kapselung und modulare Programmierung 3.3.2. Abstrakte Datentypen (ADT) . . . . . . . 3.3.3. ADT vs Objekt . . . . . . . . . . . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

II. Grundlagen der Objekt-Orientierten-Programmierung


4. Programmieren mit Objekten 4.1. Objekt-Orientiertes Software-Engineering . . . . . . . . . 4.2. Daten und Methoden . . . . . . . . . . . . . . . . . . . . . 4.2.1. Prozedurale Datenabstraktion . . . . . . . . . . . . 4.2.2. Objekte als Konstruktoren einer Datenabstraktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27
29 29 29 29 30

4.3.

4.4.

4.5.

4.6.

4.7.

4.2.3. Klassenparameter und Klassenkonstruktor . . . . . 4.2.4. Klassen und Objekte . . . . . . . . . . . . . . . . . 4.2.5. Instanzvariablen . . . . . . . . . . . . . . . . . . . 4.2.6. Getter und Setter . . . . . . . . . . . . . . . . . . . 4.2.7. Zugrismodikatoren . . . . . . . . . . . . . . . . . 4.2.8. Methoden . . . . . . . . . . . . . . . . . . . . . . . Komposition und Vererbung . . . . . . . . . . . . . . . . . 4.3.1. Begriichkeiten . . . . . . . . . . . . . . . . . . . . 4.3.2. Objekt-basierte Programmierung und Komposition 4.3.3. Objekt-orientierte Programmierung und Vererbung 4.3.4. Parameterlose Methoden . . . . . . . . . . . . . . . 4.3.5. Polymorphismus und dynamisches Binden . . . . . Mehrfachvererbung vs Traits . . . . . . . . . . . . . . . . . 4.4.1. Das Diamantenproblem . . . . . . . . . . . . . . . 4.4.2. Javas Interfaces . . . . . . . . . . . . . . . . . . . 4.4.3. Scalas Traits . . . . . . . . . . . . . . . . . . . . . 4.4.4. Linearisierung . . . . . . . . . . . . . . . . . . . . . 4.4.5. Komposition mit Traits . . . . . . . . . . . . . . . Alles ist ein Objekt . . . . . . . . . . . . . . . . . . . . . . 4.5.1. Smalltalk als Vorreiter . . . . . . . . . . . . . . . . 4.5.2. Scalas Klassenhierarchie . . . . . . . . . . . . . . . 4.5.3. Scalas Pakete . . . . . . . . . . . . . . . . . . . . . CLOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1. Jenseits der Smalltalk-Klasse . . . . . . . . . . . . 4.6.2. Multi-Methoden . . . . . . . . . . . . . . . . . . . Probleme der Objekt-orientierten Programmierung . . . . 4.7.1. Das Problem der Substituierbarkeit . . . . . . . . . 4.7.2. Das Fragile-base-class Problem . . . . . . . . . . . 4.7.3. Das Problem der schlechten Tracebarkeit . . . . . . 4.7.4. Das Problem der eindimensionalen Strukturierung 4.7.5. Das Problem der mangelnden Kapselung . . . . . . 4.7.6. Das Problem der mangelnden Skalierbarkeit . . . . 4.7.7. Das Problem der mangelnden Eignung . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

32 33 34 37 37 38 38 38 41 42 46 47 48 48 49 50 51 54 59 59 60 62 63 63 64 65 66 67 68 69 71 72 73

III. Grundlagen der Funktionalen Programmierung


5. Konzepte funktionaler Programmierung 5.1. Abgrenzung zur imperativen Programmierung 5.2. Ausgangspunkt Mathematik . . . . . . . . . . 5.2.1. Mathematische Funktionen . . . . . . 5.2.2. Rekursion . . . . . . . . . . . . . . . . 5.2.3. Lambda-Notation . . . . . . . . . . . . 5.2.4. Currying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75
77 77 78 78 81 81 82

II

5.3. Funktionale Programme . . . . . . . . . . . . . 5.3.1. Verzgerte Evaluierung . . . . . . . . . . 5.3.2. Programme als Funktionen . . . . . . . 5.3.3. Modellieren von Zustnden . . . . . . . 5.4. Funktionale Datentypen . . . . . . . . . . . . . 5.4.1. ADT und Mustervergleich . . . . . . . . 5.4.2. Unvernderliche Daten und Kollektionen 5.4.3. Monaden als Tor zur realen Welt . . . . 6. Elemente Funktionaler Programmierung in Scala 6.1. Funktionen und Closures . . . . . . . . . . . . 6.1.1. Funktionen . . . . . . . . . . . . . . . 6.1.2. Closures . . . . . . . . . . . . . . . . . 6.2. Listen und Tupel . . . . . . . . . . . . . . . . 6.2.1. Listen . . . . . . . . . . . . . . . . . . 6.2.2. Tupel . . . . . . . . . . . . . . . . . . 6.3. Case Classes und Pattern Matching . . . . . . 6.4. Konstruktoren und Extraktoren . . . . . . . . 6.5. For Ausdrcke als Syntax fr Monaden . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

86 86 87 87 89 89 90 91 95 95 95 96 98 98 101 102 106 106

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

IV. Typsysteme
7. Typsysteme 7.1. Typen vs. Typsysteme . . . . . . . . . . . . . . . . . 7.2. Grundlagen Objekt-Orientierter Typsysteme . . . . . 7.3. Scalas Typsystem . . . . . . . . . . . . . . . . . . . 7.3.1. Theoretische Grundlagen . . . . . . . . . . . 7.3.2. Typen als gleichberechtigte Sprachelemente . 7.3.3. Funktionale Typabstraktion . . . . . . . . . . 7.3.4. Typschranken und Varianzen . . . . . . . . . 7.3.5. Objekt-Orientierte Typabstraktion . . . . . . 7.3.6. Verbesserungen gegenber Javas Typsystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

109
111 111 113 115 115 116 117 118 120 121

V. Objekt-Funktionale Programmierung
8. Warum Objekt-Funktionale Programmierung? 8.1. Java-Frust . . . . . . . . . . . . . . . . . . . . . 8.2. Neue Lsungen fr alte und neue Probleme . . 8.3. Komponenten-basierte Programmierung . . . . 8.3.1. Was sind Komponenten? . . . . . . . . . 8.3.2. Abstraktionen fr Komponentensysteme 8.4. Service-Orientierte Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

123
125 125 127 128 128 128 132

III

8.5. Parallele Programmierung . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 8.5.1. Nebenlugkeit und Parallelitt . . . . . . . . . . . . . . . . . . . . 133 8.5.2. Probleme und Lsungsanstze . . . . . . . . . . . . . . . . . . . . . 134 9. Zwischenlsung oder neues Paradigma? 9.1. Objekt-Funktional vs Funktional . . . . . . . . . 9.2. Objekt-Funktional vs FRP . . . . . . . . . . . . . 9.3. Lisp oder nicht Lisp? . . . . . . . . . . . . . . . . 9.3.1. Zurck in die Zukunft - der neue Trend zu 9.3.2. Die Jahrhundertsprache . . . . . . . . . . 9.3.3. Skalpell vs Schweizer Armeemesser . . . . 9.4. PL/I, die erfolglose Jahrhundertsprache . . . . . 9.5. Scala in zwei Modi . . . . . . . . . . . . . . . . . 9.6. Fazit . . . . . . . . . . . . . . . . . . . . . . . . . 139 139 140 144 144 144 147 151 152 153

. . . . . . . . . Lisp . . . . . . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

VI. Anhang

159

A. Scala Code ausfhren 161 A.1. Der Scala Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 A.2. Scala Applikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 B. Ausnahmebehandlung in Scala C. Anwendungsgebiet XML-Verarbeitung C.1. Scalas XML Untersttzung . . . . . . . . . . C.1.1. XML - die Sprache des Web . . . . . . C.1.2. Scalas Datenmodell fr XML . . . . . C.1.3. XML-Dokumente erzeugen . . . . . . . C.1.4. XML-Dokumente einlesen . . . . . . . C.1.5. Pattern Matching auf XML . . . . . . C.1.6. XML-Queries mit For Comprehensions C.1.7. XML-basierte Web-Services . . . . . . 165 167 167 167 167 169 170 172 172 172

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

IV

Abbildungsverzeichnis
3.1. Beispiel einer Algebra fr einen vereinfachten Datentyp Liste (nach [GD04]) 3.2. Eine Matrix zur Spezikation von Beobachtungen und Konstruktoren fr Listen (nach [Coo90]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3. Die ADT-Sicht: Zerlegen der Matrix anhand der Beobachtungen (nach [Coo90]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4. Die Objekt-Sicht: Zerlegen der Matrix anhand der Konstruktoren (nach [Coo90]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1. 4.2. 4.3. 4.4. 4.5. Zerlegung einer Matrix fr eine Datenabstraktion Liste (nach [Coo90]) Ausschnitt aus der Methodenliste der Klasse Tiger . . . . . . . . . . . Systematik des Tierreiches (nach [Tie10]) . . . . . . . . . . . . . . . . Klassenbeispiel fr das Diamanten-Problem bei der Mehrfachvererbung Scalas Klassenhierarchie (nach [Ode06]) . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 24 24 24 30 41 43 49 61

5.1. Eine Datenabstraktion Liste nach Beobachtungen zerlegt (nach [Coo90]) 89 5.2. ADT-Implementierung fr eine Datenabstraktion Liste (nach [Coo90]) . 90 6.1. Scalas for Ausdruck als Syntax fr Monaden (nach [Pop10]) . . . . . . . 108 8.1. Die Technologierma Thoughtworks stuft Scala in die Kategorie trial ein (aus [Tho11]) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 C.1. Beispiel fr ein DOM (Document Object Model) (nach [App10]) . . . . . 168

Tabellenverzeichnis
4.1. Sichtbarkeitsbeschrnkungen durch [WP09]). . . . . . . . . . . . . . . . 4.2. Sichtbarkeitsbeschrnkungen durch [WP09]). . . . . . . . . . . . . . . . Zugrismodikatoren in Scala (nach . . . . . . . . . . . . . . . . . . . . . . 39 Zugrismodikatoren in Java (nach . . . . . . . . . . . . . . . . . . . . . . 40

7.1. Mglichkeiten der Abstraktion in Java und Scala. P steht fr Parameterization, AM fr Abstract Members . . . . . . . . . . . . . . . . . . . . . . 116 7.2. Code-Beispiele fr Abstraktion in Scala. . . . . . . . . . . . . . . . . . . . 116 7.3. Erlaubte Positionen kovarianter und kontravarianter Varianz-Annotationen 120 8.1. Maximale Anzahl von Threads in einer JVM (aus [Tho10]) . . . . . . . . . 135 9.1. Komplexittstypen innerhalb eines Softwaresystem (aus [MM06]). . . . . . 141

VII

Listings
2.1. Verschachtelte Sichtbarkeitsbereiche (scopes) und Verschattung (shadowing) von Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2. Imperative Kontrollstrukturen in Scala . . . . . . . . . . . . . . . . . . . . 16 4.1. Die beiden Konstruktoren der Datenabstraktion Integer-Liste als ScalaKlassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Intensionale und extensionale Identitt von Scala-Objekten . . . . . . . 4.3. Die Systematik des Tierreiches als Scala-Klassenhierarchie . . . . . . . . 4.4. Methoden- und Konstruktorenaufrufe in Linearisierungen . . . . . . . . 4.5. Wiederverwendung von Programmcode mit Scala traits . . . . . . . . . .

. . . . .

31 35 44 53 56

6.1. Funktionen in Scala. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 6.2. Listen in Scala . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 6.3. Case Classes und Pattern Matching in Scala . . . . . . . . . . . . . . . . . 103 7.1. Denition generischer Klassen und Methoden in Scala mittels TypParametrisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 A.1. Main-Objekt als Eingangspunkt einer Scala-Applikation . . . . . . . . . . 162 A.2. if-Anweisungen als guard clauses zur Behandlung von Spezialfllen . . . . 162 C.1. C.2. C.3. C.4. Beispiel fr ein XML-Dokument (nach [App10]) . . . . . . Konversion interner Datenstrukturen einer Scala-Klasse zu Beispiel fr einen Web-Service Request (nach [Col11]) . . Beispiel fr einen Web-Service Response (nach [Col11]) . . . . . . XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 169 173 173

IX

1. Einleitung
Objekt-orientierte Programmierung (OOP) und Funktionale Programmierung (FP) sind zwei bedeutende Paradigmen der Programmierung, die bereits in den 60er-Jahren entwickelt wurden. In ihrer fast 50jhrigen Geschichte haben sich beide Paradigmen zweifellos wechselseitig beeinut. Es nden sich Elemente der FP in Objekt-orientierten Sprachen, genauso wie viele funktionale Sprachen durch Elemente der OOP angereichert wurden. Dennoch haben beide Paradigmen weitgehend in ihrer jeweils eigenen Welt existiert: die FP in der Welt der Forschung, die OOP in der Welt der Anwendungsprogrammierung. Dabei hat die FP im Vergleich zur OOP ein Nischendasein gefhrt. Obwohl die Vorzge der FP von ihren Anhngern oft hervorgehoben wurden, ist die OOP stets ungleich populrer gewesen. Im Jahr 2003 wurde von Martin Odersky und seinem Team an der EPFL (in Lausanne, Schweiz) die erste Version der Sprache Scala verentlicht [Ode06]. Scala wird explizit als Objekt-funktionale Programmiersprache bezeichnet und versucht beide Paradigmen in einer einzigen Sprache zu vereinen. In dieser Arbeit werden zunchst die einzelnen Paradigmen vorgestellt, einschlielich der ursprnglichen imperativen Programmierung. Dann wird gezeigt, wie Scala diese unterschiedlichen Anstze in einer einzigen Sprache zusammenfhrt. Da in der Geschichte der Programmiersprachen schon sehr frh begonnen wurde, groe und komplizierte Softwaresysteme zu entwickeln [Seb96], waren die Bedrfnisse der Softwareindustrie stets ein wichtiges Kriterium beim Sprachdesign. In ihrem Aufsatz Out of the Tar Pit 1 identizieren Moseley and Marks unbeherrschbare Komplexitt als die Wurzel allen bels in der Softwareindustrie, als Hauptursache fr die 1968 zum ersten Mal erwhnte und bis heute nicht wirklich berwundene Softwarekrise [MM06]. Daher wird in dieser Arbeit an vielen Stellen auf die drei Hauptfaktoren schdlicher Komplexitt, den vernderbaren Zustand, den Codeumfang, und den Kontrollu, sowie auf damit in Zusammenhang stehende ungelste (bzw nicht zufriedenstellend gelste) Probleme der Softwareindustrie eingegangen. Durch Vergleiche mit konkurrierenden neuen Sprachen wie bspw Clojure sowie durch die Gegenberstellung mit radikal anderen Anstzen aus der Geschichte und Gegenwart der Programmiersprachen soll evaluiert werden, ob Scala und die Objekt-funktionale Programmierung entscheidend dazu beitragen knnen, die Komplexitt der Softwareentwicklung beherrschbar zu machen. Zudem wird diskutiert, ob der Ansatz von Scala zukunftsweisend sein knnte, oder ob es sich eher um eine bergangslsung auf dem Weg zu einer ganz anderen Jahrhundertsprache handelt - einer Sprache, wie sie von Paul Graham beschrieben wurde [Gra03].
1

Raus aus der Teergrube

1. Einleitung Im Teil I auf Seite 5 der Arbeit wird gezeigt, wie sich die zustandsbehaftete, durch explizite Steuerung des Kontrollusses gekennzeichnete imperative Programmierung aus den an die Hardwarearchitektur der Von-Neumann-Rechner angepaten Maschinensprachen entwickelt hat, und wie sich im Laufe der Zeit Konzepte zur Abstraktion und Strukturierung von Programmen herausgebildet haben, die an die Bedrfnisse menschlicher Programmierer angepate Sprachen ermglichen. Teil II auf Seite 29 widmet sich dann der Objekt-orientierten Programmierung, die aus der imperativen Programmierung hervorgegangen ist und diese mit vielen neuen Konzepten fr die Ingenieurs-mige Entwicklung groer Softwaresysteme angereichert hat. Da Scala eine vollstndig Objekt-orientierte Sprache ist (jedes Sprachelement ist ein Objekt), lassen sich die Konzepte dieses Paradigmas gut an einfachen Scala-Beispielen erklren. Die Probleme der Objekt-orientierten Programmierung sind die Ursachen der Softwarekrise unserer Zeit und wichtigster Ansporn fr die gegenwrtige Renaissance in der Entwicklung von Programmiersprachen. Sie werden im Kontext des Grundbels Komplexitt und im Hinblick auf Scalas Lsungsanstze diskutiert. Da nicht alle Versprechen der Objekt-orientierten Programmierung eingehalten wurden, gewinnt die mathematisch fundierte, von der Hardwarearchitektur losgelste funktionale Programmierung heute wieder an Bedeutung. Teil III auf Seite 77 erklrt die Grundlagen dieses Paradigmas, warum es fr die Eindmmung schdlicher Komplexitt so ntzlich ist, und wie die Elemente funktionaler Programmierung in die Objektorientierte Sprache Scala eingebaut wurden. Teil IV auf Seite 111 geht auf eine der besonderen Strken Scalas ein - sein mchtiges und, aufgrund von Typinferenz, dennoch im Hintergrund bleibendes Typsystem. Es ist dieses Typsystem, was in Scala ganz neue Mglichkeiten des abstrakten, Komponentenbasierten Software-Designs ermglicht. In Teil V auf Seite 125 wird schlielich gezeigt, wie die Zusammenfhrung zweier Hauptstrnge in der Geschichte der Programmiersprachen im Paradigma der Objektfunktionalen Programmierung zu neuen Lsungen fr neue und alte Probleme der Software-Entwicklung fhren kann. Auerdem wird Scalas Ansatz mit dem anderer Sprachen verglichen und, zumindest ansatzweise, versucht herauszuarbeiten wohin die Entwicklung der Programmiersprachen in Zukunft gehen knnte, welche Kriterien dabei eine Rolle spielen werden, und welchen Stellenwert Scala und die Objekt-funktionale Programmierung dabei einnehmen knnten. Im Anhang C auf Seite 167 ndet sich noch eine Beschreibung von Scalas (eingebauten) Mglichkeiten zu Be- und Verarbeitung von XML, die auch den Einsatz vieler im Text vorgestellter Sprachelemente zeigt. Da Scala sehr viele Konzepte in sich vereint, werden fast alle Code-Beispiele in dieser Arbeit in Scala gegeben. Auch wenn beispielsweise Variablen und Kontrollanweisungen im historischen Kontext der imperativen Programmiersprachen besprochen werden, wird in den Beispielen die Scala-Syntax benutzt. Dadurch bleibt der Leser von der Syntax vieler fr die Praxis derzeit kaum noch relevanter Sprachen verschont.

Teil I.

Ausgangspunkt Imperative Programmierung

2. Elemente der Imperativen Programmierung


2.1. Entstehung und Erfolgsfaktoren
Seit den 40er Jahren werden Computer nahezu ausschlielich als Von-Neumann-Rechner konstruiert [Seb96]. Diese Architektur begnstigte die Entstehung der imperativen Programmierung als ursprnglicher Form der Programmierung. Ihre wesentliche Elemente sind: Variablen zur Modellierung von Speicherzellen. Zuweisungsoperationen die auf der bertragung von Daten und Anweisungen zwischen Speicher und CPU beruhen. Iterationen zur ezienten Wiederholung von in benachbarten Speicherzellen abgelegten Anweisungen. Die imperative Programmierung ist eng mit der Hardwarearchitektur des Rechners verbunden, ihre lange Erfolgsgeschichte hat aber nach Watt auch noch einen anderen Grund: Programme werden geschrieben, um Prozesse der realen Welt zu modellieren, die mit Objekten der realen Welt umgehen. Weil der Zustand dieser Objekte sich oft mit der Zeit ndert, kann man sie ganz natrlich als Variablen darstellen und die Prozesse ganz natrlich mit imperativen Programmen modellieren. (aus [Wat96]) Obwohl die Programmierung mit zustandsbehafteten Variablen bereits auf natrliche Weise die Modellierung der realen Welt im Kleinen (also innerhalb einzelner Programme berschaubarer Gre) untersttzt, mute diese Idee in der Objekt-orientierten Programmierung durch die Einfhrung neuer Konzepte ergnzt werden, um auch fr die Modellierung im Groen, die Ingenieurs-mige Entwicklung groer und komplexer Softwaresysteme im Team, geeignet zu sein (siehe Abschnitt 4.1 auf Seite 29).

2.2. Variablen
2.2.1. Eigenschaften von Variablen
Nach Sebesta [Seb96] sind imperative Programmiersprachen gewissermaen Abstraktionen der zugrundeliegenden Von-Neumann-Rechnerarchitektur. Die Speicherzellen der

2. Elemente der Imperativen Programmierung Hardware werden durch Variablen mit folgenden Eigenschaften reprsentiert: Name Adresse Wert Typ Lebensdauer Sichtbarkeit (Scope)

2.2.2. Name
Namen sind Zeichenfolgen zur Identizierung von Variablen und anderen Bestandteilen eines Programms (zB Typen, Werten, Methoden und Klassen), die laut Odersky [Ode09b] in Scala unter dem Begri Entitten zusammengefat werden. In Scala beginnen alpha-nummerische Namen mit einem Buchstaben oder dem Unterstrich. Darauf folgt eine beliebige Anzahl weiterer Buchstaben, Zahlen oder Unterstriche. Da der Unterstrich in Scala ein besonderes Zeichen ist (es ersetzt das sonst bliche Sternchen * als Wildcard-Zeichen), sollte er in Namen nur mit Vorsicht verwendet werden. Die Namenskonventionen von Scala orientieren sich an Java: Namen von Feldern, Methodenparametern, lokalen Variablen und Funktionen beginnen mit Kleinbuchstaben, Namen von Traits und Klassen1 mit Grobuchstaben. Gro- und Kleinschreibung ist relevant, zusammengesetzte Namen werden als ein Wort mit eingebetteten Grobuchstaben geschrieben (CamelCase ). Das Dollarzeichen $ zhlt als Buchstabe, sollte aber von Programmierern nicht in Namen verwendet werden, da dies zu Konikten mit Compilergenerierten Namen fhren kann. In Scala gibt es eine Reihe reservierter Wrter, die nicht als alpha-nummerische Namen verwendet werden drfen:

abstract do nally import object requires throw val


1

case else for lazy override return trait var

catch extends forSome match package sealed try while

class false if new private super true with

def nal implicit null protected this type yield

Diese Entitten werden hier nur aufgezhlt und nicht weiter erklrt

2.2. Variablen

2.2.3. Adresse und Wert


Die Adresse einer Variablen ist nach Sebesta [Seb96] die Speicheradresse, mit der sie assoziiert ist. Der Wert einer Variablen ist der Inhalt der assoziierten Speicherzellen. Da Programmvariablen meist mehr als den von einer einzelnen physischen Hauptspeicher-Zelle bereitgestellten Speicherplatz bentigen, ist es zweckmig in abstrakten Speicherzellen zu denken, die denitionsgem genau den von der assoziierten Variablen bentigten Speicherplatz zur Verfgung stellen. In Scala gibt es zwei Arten von Variablen, die sich in ihrem Verhalten bezglich der assoziierten Speicheradresse (Referenz ) unterscheiden: vars und vals. Obwohl die vals aus der funktionalen Programmierung stammen, werden sie der Einfachheit halber schon hier vorgestellt. Beide Variablentypen haben Referenzsemantik, sie enthalten also aus Sicht des Programmierers eine Referenz auf einen Wert und nicht den Wert selber (Wertsemantik ). var entspricht einer Variable in der imperativen Programmierung. Ihre Referenz ist vernderlich, dh derselben Variablen knnen nacheinander verschiedene Speicheradressen zugewiesen werden. val entspricht einer Variablen in der funktionalen Programmierung. Ihre Referenz ist unvernderlich, dh sie ist nach der ersten Zuweisung immer mit der gleichen Speicheradresse assoziiert. Der dort abgespeicherte Wert kann sich jedoch ndern, ist also vernderlich. Hier einige Beispiele fr Variablennamen und die Verwendung von var und val in Scala (der Scala-Compiler erkennt, da in den Beispielen mit Integer-Werten gearbeitet wird, dies mu also nicht extra angegeben werden):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

scala> var myVar = 3 //Zuweisung myVar: Int = 3 scala> myVar = 5 //erneute Zuweisung erfolgreich myVar: Int = 5 scala> val myVal = 3 //Zuweisung myVal: Int = 3 scala> myVal = 5 //erneute Zuweisung erzeugt Fehler <console>:5: error: reassignment to val myVal = 5 ^ scala> val myArrayVal = Array(1, 2, 3) //Zuweisung myArrayVal: Array[Int] = Array(1, 2, 3) scala> myArrayVal = Array(4, 5, 6) //erneute Zuweisung erzeugt Fehler <console>:5: error: reassignment to val

2. Elemente der Imperativen Programmierung


20 21 22 23 24 25

myArrayVal = Array(4, 5, 6) ^ scala> myArrayVal(1) = 0 //Wert aendern scala> myArrayVal //unveranderliche Referenz, veraenderliche Werte res3: Array[Int] = Array(1, 0, 3)

2.2.4. Typ
Computerprogramme erzeugen Ergebnisse, indem sie Daten manipulieren [Seb96]. Es wird daher allgemein als wichtig angesehen, da sie eine groe Bandbreite an Datentypen bereitstellen, um die Probleme der realen Welt geeignet mit Variablen modellieren zu knnen (nach [Wat96]): Einfache Typen enthalten atomare Werte, die nicht mehr in einfachere Werte unterteilt werden knnen. Dazu gehren beispielsweise die in Programmiersprachen allgegenwrtigen boolschen Wahrheitswerte (true und false), ganze und reelle Zahlen sowie Zeichen (characters). Etwas spezieller sind Aufzhlungstypen (enumeration types) und Unterbereichstypen (subrange types). Zusammengesetzte Typen sind Typen, deren Werte aus einfacheren Werten zusammengefgt werden. Kartesische Produkte enthalten aus den Werten mehrerer (mglicherweise verschiedener) Typen zusammengefgte Wertekombinationen (Paare, Tripel, Quadrupel etc). Scalas Tupel sind ein Beispiel fr kartesische Produkte. Disjunkte Vereinigungen enthalten Werte, die aus den Werten mehrerer (meistens verschiedener) Typen komponiert sind und von denen jeweils ein Wert nur eines bestimmten Typs ausgewhlt wird. Als Beispiel kann die Denition abstrakter Klassen mit case classes in Scala genannt werden (siehe Abschnitt 6.3 auf Seite 102). Abbildungen bilden die Werte einer Ursprungsmenge auf die Werte einer Bildmenge ab. Funktionsabstraktionen sind die klassischen Implementierungen von Abbildungen in Programmiersprachen. Aber auch Felder lassen sich als Abbildungen einer Indexmenge auf eine Komponentenmenge verstehen. Scalas Arrays und Maps gehren zu den Abbildungen. Rekursive Typen sind Typen deren Werte andere Werte desselben Typs enthalten knnen. Listen sind (nicht nur in Scala) der wichtigste rekursive Datentyp. Die Implementierung vieler verschiedener Datentypen im Kern einer Programmiersprache kann aber auch kritisch gesehen werden (siehe Abschnitt 9.3.2 auf Seite 144). Sie dient oftmals weniger der einfacheren Modellierung als vielmehr der Geschwindigkeit der Programmausfhrung, stellt also eine Art vorzeitige Optimierung dar, die zustzliche

2.2. Variablen und vielleicht unntige Komplexitt bei Entwurf und Anwendung einer Programmiersprache erzeugt. Abschnitt 9.3.3 auf Seite 147 stellt als Alternative eine minimalistische Programmiersprache vor, die mit gerade einmal drei Datentypen auskommt: Nummern, Symbolen und Listen.

2.2.5. Lebenszeit
Die Lebenszeit einer Variablen ist ein zeitliches Konzept. Es ist dennoch eng verbunden mit dem Speicherort, an den eine Variable gebunden wird [Seb96], denn bestimmte Speicherorte stehen fr bestimmte Lebenszeiten. Auf dem stack endet die Lebenszeit einer (lokalen) Variable, wenn die Methode, in der sie deniert ist, beendet wird. Im heap (Haufenspeicher) existiert eine Variable, bis sie explizit gelscht oder von der garbage collection eingesammelt wird. Die (abstrakte) Speicherzelle, an die eine Variable gebunden wird, entstammt einem Pool verfgbaren Speichers. Der Proze des Bindens nennt sich Allokation, das Zurcklegen einer nicht mehr gebundenen Zelle in den Pool verfgbarer Speicherzellen Deallokation.

2.2.6. Sichtbarkeit
Die Sichtbarkeit bzw der Scope einer Variablen ist ein rumliches Konzept. Es bezeichnet den Bereich aller Programmanweisungen, in denen eine Variable sichtbar (dh referenzierbar) ist [Seb96]. Die Sichtbarkeitsregeln einer Sprache bestimmen, wie in einer konkreten Situation ein Name mit einer Variablen assoziiert wird. Das Wissen um diese Regeln ist essentiell, um Programme schreiben und lesen zu knnen. Whrend die Sichtbarkeit lokaler Variablen durch die Sprache vorgegeben wird, kann der Programmierer bei den von ihm explizit per Befehl erzeugten Variablen die Sichtbarkeit durch Zugrismodikatoren (access-modiers) einschrnken (siehe Abschnitt 4.2.7 auf Seite 37). In Scala lt sich die Sichtbarkeit lokaler Variablen gut anhand der durch geschweifte Klammern umschlossenen Blcke erlutern. Jedes Paar geschweifter Klammern fhrt einen neuen Sichtbarkeitsbereich (scope) ein. Innerhalb eines Sichtbarkeitsbereichs, der ja auch als Namensraum fungiert, knnen keine zwei gleichnamigen Variablen deniert werden. In Scala kann (anders als in Java) in einem inneren Scope eine Variable deniert werden, die den gleichen Namen hat wie eine Variable in einem ueren Scope. Die innere Variable verschattet (shadows) dann die gleichnamige uere Variable, macht sie also im inneren Scope unsichtbar [OSV08]. Listing 2.1 zeigt, wie jede der drei verschachtelten Funktionen einen neuen Namensraum deniert und die Variable x jeweils die gleichnamigen Variablen in ueren Scopes verschattet (unsichtbar macht).
1 2 3 4 5 6

object Scope extends Application { def outermost() { var x = 1 println("outermost x: " + x) outer def outer() {

2. Elemente der Imperativen Programmierung


7 8 9 10 11 12 13 14 15 16 17

var x = 2 println("outer x: " + x) inner def inner() { var x = 3 println("inner x: " + x) } } } outermost }

Listing 2.1: Verschachtelte Sichtbarkeitsbereiche (scopes) und Verschattung (shadowing) von Variablen Die Ausgabe von Listing 2.1 auf der vorherigen Seite sieht folgendermaen aus:
1 2 3

outermost x: 1 outer x: 2 inner x: 3

2.3. Zuweisungsoperation
Die Zuweisungsoperation ist ein zentrales Element imperativer Programmiersprachen. Mit ihr kann der Programmierer in dynamischer Weise die Bindung zwischen Variablen und Werten verndern [Seb96]. Die Zuweisung zu einer einfachen Variable hat in Scala folgende allgemeine Form x = e, wobei x ein var oder val sein kann und e fr einen gltigen Scala-Ausdruck (expression) steht. Hier einige Beispiele fr Zuweisungen an einfache Variablen in Scala:
1 2 3 4 5 6 7 8 9 10 11

scala> var x = 1 x: Int = 1 scala> x = x + 1 x: Int = 2 scala> val y = "String" y: java.lang.String = String scala> val z = y + " concatenation" z: java.lang.String = String concatenation

Die Beispiele zeigen, da das Pluszeichen + in Scala (wie in Java) ein berladener Operator ist, der sowohl fr die mathematische Addition als auch fr die Verkettung von Strings verwendet wird. Auerdem wird deutlich, da in Scala wie auch in Java und anderen CNachfolgesprachen das Gleichheitszeichen = als Zuweisungsoperator benutzt wird, obwohl

10

2.3. Zuweisungsoperation es zur Verwechslung mit einem Test auf Gleichheit einldt und in der Geschichte der Programmierung schon fr viele Programmierfehler verantwortlich war [SKS09]. Odersky begrndet diese Designentscheidung als pragmatisches Zugestndnis an die Hauptzielgruppe von Scala, die Java-Programmierer: There was one thing that we changed from a more pure initial design. Initially we had colon-equals for assignmentjust as in Pascal, Modula, and Adaand a single equals sign for equality. A lot of programming theorists would argue that thats the right way to do it. Assignment is not equality, and you should therefore use a dierent symbol for assignment. But then I tried it out with some people coming from Java. The reaction I got was, Well, this looks like an interesting language. But why do you write colon-equals? What is it? And I explained that its like that in Pascal. They said, Now I understand, but I dont understand why you insist on doing that. Then I realized this is not something we wanted to insist on. We didnt want to say, We have a better language because we write colon-equals instead of equals for assignment. Its a totally minor point, and people can get used to either approach. So we decided to not ght convention in these minor things, when there were other places where we did want to make a dierence. (aus [VS09]) Die so unscheinbar und harmlos wirkende Zuweisungsoperation ist allerdings nicht nur mit dem Fluch belegt, in vielen Sprachen durch das gnzlich ungeeignete Gleichheitszeichen reprsentiert zu werden. Sie ist trotz ihrer Primitivitt die zentrale Anweisung imperativer Sprachen, wie folgendes Zitat von Backus verdeutlicht: Conventional programming languages are basically high level, complex versions of the von Neumann computer. [...] Von Neumann programming languages use variables to imitate the computers storage cells; control statements elaborate its jump and test instructions; and assignment statements imitate its fetching, storing, and arithmetic. [...] Moreover, the assignment statement splits programming into two worlds. The rst world comprises the right sides of assignment statements. This is an orderly world of expressions, a world that has useful algebraic properties (except that those properties are often destroyed by side eects). It is the world in which most useful computation takes place. The second world of conventional programming languages is the world of statements. The primary statement in that world is the assignment statement itself. All the other statements of the language exist in order to make it possible to perform a computation that must be based on this primitive construct: the assignment statement. (aus [Bac07]) Die Zuweisungsoperation verndert den Zustand von Variablen, und vernderbarer Zustand ist laut Moseley and Marks die mit Abstand wichtigste Ursache fr die nicht mehr beherrschbare Komplexitt groer Softwaresysteme [MM06]. Die Anwesenheit von

11

2. Elemente der Imperativen Programmierung Zustand macht es schwierig oder unmglich, Programme zu verstehen. Beim Testen von zustandsbehafteten Programmen erzeugt die Kombination einer riesigen Menge mglicher Eingabedaten mit einer noch greren Menge mglicher Zustnde soviel Unsicherheit, da nur wenig bleibt, was man mit Sicherheit ber das System sagen kann. Zustand erschwert es zudem den Entwicklern, informell ber ein Programm nachzusinnen. Wenn die Anzahl mglicher Zustnde wchst, stt die menschliche Fhigkeit, das erwartete Verhalten eines Programmes anhand mentaler Fall- fr Fall Simulationen nachzuvollziehen, schnell an seine Grenzen. Programmiersprachen knnen auch ganz auf Zustandsvariablen und Zuweisungsoperation verzichten und damit die genannten Probleme vermeiden, wie folgendes Zitat aus Wikipedia zeigt: Any assignment that changes an existing value (e.g. x := x + 1) is disallowed in purely functional languages. In functional programming, assignment is discouraged in favor of single assignment, also called name binding or initialization. Single assignment diers from assignment [...] in that it can only be made once, usually when the variable is created; no subsequent re-assignment is allowed. Once created by single assignment, named values are not variables but immutable objects. (aus [Wik11]) In Teil III auf Seite 77 wird die aufgrund ihrer den vernderbaren Zustand und damit bermige Komplexitt vermeidenden Eigenschaften zunehmend an Bedeutung gewinnende funktionale Programmierung ausfhrlich vorgestellt. In Teil V auf Seite 125 wird dann dargestellt, wie die Objekt-funktionale Programmierung Elemente aus der funktionalen Programmierung bernommen hat, ohne die Praktikabilitt der Objektorientierten Programmierung aufzugeben.

2.4. Kontrollanweisungen
2.4.1. Steuerung des Kontrollusses
Imperative Programmiersprachen werten Ausdrcke (siehe Abschnitt 5.1 auf Seite 77) aus und weisen die resultierenden Werte Variablen zu [Seb96]. Um exible und mchtige Programme schreiben zu knnen, werden aber zustzlich zum Abarbeiten der Programmanweisungen in textueller Reihenfolge noch spezielle Kontrollanweisungen (control statements) bentigt. Diese dienen der Auswahl (selection) zwischen alternativen Kontrollssen und der Wiederholung (repetition) von (Gruppen von) Anweisungen. Obwohl jede Programmausfhrung notwendigerweise eine gewisse Reihenfolge der Aktionen impliziert, betrachten Moseley and Marks den Kontrollu nicht als essentiellen Bestandteil eines Programmes, sondern als einen Faktor der berssige Komplexitt (accidental complexity) in ein System hineintrgt [MM06]. In einer idealen Welt sollten sich Programmierer bei der Software-technischen Lsung eines Problems nicht mit dem Kontrollu beschftigen mssen. Erst wenn es um die Optimierung der Performance geht, sollte Kontrollu eine Rolle spielen, ohne jedoch fr die eigentliche Problemlsung

12

2.4. Kontrollanweisungen essentiell zu sein. Damit lehnen sich die beiden Autoren an die in dieser Arbeit nicht nher behandelte logische Programmierung an, die in ihrer reinen Form in der Spezizierung logischer Axiome (Aussagen ber das Problem) ohne Bercksichtigung des Kontrollusses besteht. Sie kritisieren besonders den in den meisten Programmiersprachen eingebauten und implizit stets vorhandenen sequentiellen Kontrollu (siehe Abschnitt 2.4.2), der es Programmierern unmglich macht nichts ber die Reihenfolge der Programmanweisungen auszusagen.

2.4.2. Sequenz
Die implizit immer vorhandene grundlegende Kontrollstruktur fast aller Programmiersprachen ist die Sequenz. Bei Abwesenheit aller anderen Kontrollstrukturen werden die Anweisungen eines Programmes von oben nach unten abgearbeitet, in der Reihenfolge wie der Programmierer sie im Programmtext hingeschrieben hat [Seb96]. Obwohl diese sequentielle Abarbeitung von Programmtext beinahe so natrlich erscheint wie die Luft zum atmen, gibt es durchaus gute Grnde sie zu hinterfragen [MM06]. Die Sequenz als implizit festgelegte Kontrollstruktur fast aller Programmiersprachen zwingt den Programmierer dazu, auch dann Aussagen ber den Kontrollu zu machen (also darber wie das System funktionieren soll), wenn er eigentlich nur hinschreiben will was das System machen soll, und die Reihenfolge dabei egal ist. Moseley and Marks geben folgendes Beispiel, bei dem in drei Programmanweisungen die Beziehungen zwischen bestimmten Werten eines Programms festgelegt wird, ohne da die Reihenfolge der Programmausfhrung eine Rolle spielen wrde [MM06]:
1 2 3

a = b + 3 // im Original := als Zuweisungsoperator c = d + 2 e = f * 4

Sie argumentieren, da der Programmierer in einem solchen Fall aufgrund der impliziten Kontrollstruktur Sequenz gezwungen ist, das Problem zu berspezizieren (overspecify). Er mu eine Reihenfolge festzulegen, obwohl sie ihn eigentlich gar nicht interessiert. Paradoxerweise mu dann oftmals ber den Compiler nachtrglich aufwendig dafr gesorgt werden, da die festgelegte Reihenfolge letztendlich doch ignoriert werden kann. Zudem wird Lesern des Programmtextes das informelle Nachsinnen ber das Programm erschwert, da sie zunchst einmal davon ausgehen mssen, da die Reihenfolge der Anweisungen eine Bedeutung hat, nur um dann durch tiefere Inspektion des Programms herauszunden, da sie egal ist. Sie mssen also gedanklich die eigentlich schon berssige Arbeit des Compilers nachvollziehen. Das Problem liegt nicht im Programmtext - der Programmierer mu den Programmtext ja schlielich in einer bestimmten Reihenfolge hinschreiben. Es liegt vielmehr in der Semantik der Programmiersprachen, die die textuelle Reihenfolge der Anweisungen als Laufzeit-Sequenz bernehmen. Das Konzept der reinen logischen Programmierung zeigt, da es auch anders geht [MM06]. Es besteht darin, rein deklarativ Aussagen ber das Problem und angestrebte Lsungen zu machen. Ein Satz von Axiomen beschreibt das Problem und die Attribute

13

2. Elemente der Imperativen Programmierung mglicher Lsungen, und die Infrastruktur des ausdrcklich nicht auf der zustandsbehafteten Von-Neumann-Rechnerarchitektur basierenden Systems konstruiert zur Laufzeit einen formalen Beweis jeder Lsung. Real existierende logische Programmiersprachen, wie zB Prolog, knnen dieses Ideal nicht ganz erfllen. Es ist mglich, da dasselbe Prolog -Programm korrekt und unkorrekt sein kann, je nachdem auf welche Art es gelesen wurde. Dennoch vermeidet der konsequent deklarative Ansatz der logischen Programmierung einen Groteil der durch den impliziten und expliziten Kontrollu in den anderen Programmierparadigmen erzeugten (oft zuflligen und berssigen) Komplexitt.

2.4.3. Auswahl
Auswahlanweisungen werden in zwei Kategorien eingeteilt: Zwei-Wege-Auswahlanweisungen (two-way selectors) n-Wege-Auswahlanweisungen (n-way bzw multiple selectors) Zwei-Wege-Auswahlanweisungen werden in Scala, wie in den meisten anderen Programmiersprachen auch, mit if-else-Anweisungen realisiert (siehe Listing 2.2 auf Seite 16). Ein-Wege-Auswahlanweisungen entstehen, wenn die else-Klausel weggelassen wird (siehe Listing A.2 auf Seite 162). Als Beispiel fr einen multiplen Selektor kann der switch Befehl aus Java angefhrt werden. Er hat folgende allgemeine Form (aus [SB08]):
1 2 3 4 5

switch (expression) { case constant1: code block case constant2: code block default: code block }

Javas switch Befehl ist ziemlich beschrnkt und hat die unangenehme Eigenschaft des falling-through. Letzteres bedeutet, da nach der Auswahl von constant1 und der Ausfhrung des zugehrigen code block im obigen Beispiel auch noch die Code Blcke von constant2 und default ausgefhrt werden, wenn kein break Befehl verwendet wird. Programmierer sind aber Menschen, weswegen der break Befehl auch mal vergessen wird, was dann zwar zu seltsamen Programmverhalten, aber nicht unbedingt zu einer Fehlermeldung fhrt. Die wichtigsten Beschrnkungen von Javas switch Befehl sind: expression mu zu einem der folgenden primitiven Datentypen ausgewertet werden: char, byte, short, int, enum. constant mu zu demselben Typ ausgewertet werden, der in expression verwendet wird. switch kann nur auf Gleichheit prfen, die anderen relationalen Operatoren sind nicht anwendbar (zB <, >= etc).

14

2.4. Kontrollanweisungen Folgendes Beispiel zeigt die Anwendung des Scala-Ausdrucks match im Sinne von Javas switch Befehl. Scalas match ist aber unvergleichlich mchtiger und exibler und dabei auch noch weniger fehleranfllig als Javas switch (siehe Abschnitt 6.3 auf Seite 102). Er kann als eine Generalisierung von switch angesehen werden [OSV08]. Die Einrckungen mittels | | im Methodenkrper von checkInt haben keine inhaltliche Bedeutung. Durch sie zeigt der Scala Interpreter (siehe Abschnitt A.1 auf Seite 161) an, da eine Anweisung sich ber mehr als eine Zeile erstreckt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

scala> val x = 2 x: Int = 2 scala> def checkInt(x: Int): String = x match { | | case 0 => "x is 0" | | case 1 => "x is 1" | | case 2 => "x is 2" | | case _ => "x not in (0,1,2)" | | } checkInt: (x: Int)String scala> checkInt(x) res1: String = x is 2 // kein "fall-through" auch ohne break-Befehl scala> var y = 0 y: Int = 0 scala> checkInt(y) res2: String = x is 0 // Der Unterstrich _ als Platzhalter fuer alle nicht // explizit angegebenen Faelle (im default-case) scala> y = 5 y: Int = 5 scala> checkInt(y) res3: String = x not in (0,1,2)

Man erkennt in dem Beispiel, da in Scala kein break Befehl bentigt wird, da der match Ausdruck nach dem ersten Treer die anderen Flle nicht mehr abarbeitet. Anstelle des default Schlsselwortes wird in Scala der Unterstrich _ eingesetzt um alle nicht angegebenen Flle abzudecken.

2.4.4. Iteration
Iterative Anweisungen bewirken, da ein Befehl oder eine Gruppe von Befehlen keinmal, einmal oder mehrmals ausgefhrt wird [Seb96]. Iterative Kontrollstrukturen unterscheiden sich in zwei Punkten:

15

2. Elemente der Imperativen Programmierung Wie wird die Iteration kontrolliert? Durch einen Zhler : for Logisch : while und do-while Wo erscheint der Kontrollmechanismus innerhalb der Schleife? Am Kopf der Schleife: for und while Am Fu der Schleife: do-while Es gibt in einigen Sprachen noch andere iterative Anweisungen wie do und loop, auf die hier nicht eingegangen werden soll. Scala hat nur zwei in die Sprache eingebaute iterative Anweisungen: while und for. Andere Anweisungen werden in Bibliotheken bereitgestellt. Scalas for Ausdruck ist das funktionale Gegenstck zur klassischen imperativen for Schleife, ist aber unvergleichlich mchtiger als diese. Ihm knnen beispielsweise Scalas Funktionen erster Klasse als Argumente bergeben werden (siehe Abschnitt 6.5 auf Seite 106). Listing 2.2 zeigt die Anwendung imperativer Kontrollstrukturen in einem ScalaProgramm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

object IterativeControlStruct extends Application { def iterate(u: Int, v: Int): Int = { var x = u var y = v if (x > y) { while (x > y) { println("while entered") x = x - 1 println("x is " + x) } } else { for (i <- 1 to y) { println("for entered") y = y - 1 println("y is " + y) x = x + x println("x is " + x) } } return x } println("iterate(5,2)") println(iterate(5,2)) println("iterate(2,5)") println(iterate(2,5)) }

Listing 2.2: Imperative Kontrollstrukturen in Scala

16

2.4. Kontrollanweisungen Die while Schleife wird so lange durchlaufen, bis die (logische) Bedingung nicht mehr erfllt ist, die for Schleife solange, bis der Zhler zur angegebenen Unter- oder Obergrenze hochgezhlt wurde. Die Ausgabe von Listing 2.2 auf der vorherigen Seite sieht folgendermaen aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

iterate(5,2) while entered x is 4 while entered x is 3 while entered x is 2 2 iterate(2,5) for entered y is 4 x is 4 for entered y is 3 x is 8 [...] for entered y is 0 x is 64 64

Scala besitzt auch eine foreach Methode, mit der sich sehr bequem ber alle Elemente einer Kollektion iterieren lt [OSV08]. Kollektionen sind Datenstrukturen, die als Container fr andere Datentypen oder -strukturen fungieren [WP09]. Abschnitt 2.2.4 auf Seite 8 gibt einen kurzen berblick ber solche zusammengesetzten Datentypen.
1 2 3 4 5 6 7 8 9 10

scala> val s = "String" s: java.lang.String = String scala> s.foreach(println) S t r i n g

foreach ist aber eine Methode, die auf Strings, Arrays und anderen Kollektionen deniert ist, und keine in die Sprache eingebaute iterative Kontrollstruktur von Scala.

17

3. Abstraktion und Strukturierung


3.1. Abstraktion in der Programmierung
Abstraktion ist nach Watt die Grundlage von Philosophie und Mathematik und ein fr die Informatik und viele andere Wissenschaften elementares Konzept: Abstraktion ist die Denkweise, mit der wir uns auf allgemeine Ideen konzentrieren, anstatt auf deren konkrete Ausprgungen. (aus [Wat96]) In der Systemanalyse dient die Abstraktion dazu, sich auf die wesentlichen Aspekte des jeweiligen Problems zu konzentrieren und alle unwesentlichen Einzelheiten zu ignorieren. Beim Programmieren deutet Abstraktion auf die Unterscheidung zwischen zwei Fragestellungen hin [Wat96]: Was tut ein Programmstck? Wie ist es implementiert? Abstraktion ist eine Wae im Kampf gegen die Komplexitt des Programmierens. Sie vereinfacht den Proze des Programmierens, da sich der Programmierer mehr auf die Frage des was als auf die Frage des wie konzentrieren kann [Seb96]. Abstraktionen lassen sich verschiedenen Ebenen zuordnen, je nachdem, wieviel Information in der Abstraktion enthalten ist [Lou03]: Basis-Abstraktionen (basic abstractions). Primitive Datentypen, Variablen und einfache Anweisungen in hheren Programmiersprachen sind Abstraktionen von Computerinternen Reprsentationen von Daten, von Speicherzellen-Adressen und von Kombinationen von Maschinen-Anweisungen. Struktur-Abstraktionen (structured abstractions). Datenkollektionen wie Arrays, Kontrollstrukturen wie die if- oder while-Anweisung sowie Prozeduren und Funktionen enthalten Informationen ber Daten und Prozesse eines Programmes. Programmeinheit-Abstraktionen (unit abstractions). Module, Klassen und Pakete enthalten Information ber eigenstndige Programm-Einheiten, die unabhngig kompiliert, wiederverwendet und in Bibliotheken zusammengefat werden knnen. Die beiden fundamentalen Arten der Abstraktion in modernen Programmiersprachen sind die Proze- und die Datenabstraktion, die insbesondere auf den Ebenen der Programmstruktur und der Programmeinheiten stattnden [Seb96].

19

3. Abstraktion und Strukturierung

3.2. Prozeabstraktion
In der Geschichte der Programmiersprachen wurde zunchst die Prozeabstraktion als wichtiges Konzept wahrgenommen und umgesetzt. Prozeabstraktion beruht auf der Einfhrung von Unterprogrammen (subprograms) und ihrer Parametrisierung. Unterprogramme haben nur einen Eintrittspunkt. Sie bernehmen whrend der Zeit ihrer Ausfhrung die Programmkontrolle vom aufrufenden Hauptprogramm und geben sie nach ihrer Beendigung an den Aufrufer zurck. Unterprogramme knnen im Programmtext durch ihren Namen ersetzt und ausgelst werden und tragen somit zur besseren Lesbarkeit und zur Wiederverwendbarkeit von Programm-Code bei [Seb96]. Die frhste Form der Prozeabstraktion war die Prozedurabstraktion, die in rudimentrer Form sogar schon in Assemblersprachen existierte [Gro90]. Eine Prozedurabstraktion verkrpert einen auszufhrenden Befehl. Bei jedem Aufruf berschreibt sie Variablen. Der Benutzer der Prozedurabstraktion beobachtet nur dieses berschreiben, nicht die Schritte, die [es] bewirken. (aus [Wat96]) Der Aufrufer benutzt den Namen der Prozedur um die Ausfhrung der in ihr enthaltenen Anweisungen anzustoen. ber Parameter kann er die Ausfhrung an seine Bedrfnisse anpassen. Der nchste Mechanismus der Prozeabstraktion, der ebenfalls schon sehr frh Eingang in den Entwurf von Programmiersprachen fand, ist die Funktionsabstraktion. Eine Funktionsabstraktion verkrpert einen auszuwertenden Ausdruck. Bei jedem Aufruf liefert sie einen Ergebniswert zurck. Der Benutzer der Funktionsabstraktion beobachtet nur das Ergebnis, nicht die Schritte, mit denen es [berechnet] wurde. (aus [Wat96]) Eine Funktion hat, ganz hnlich wie eine Prozedur, formale Parameter, die beim Aufruf durch aktuelle Parameter ersetzt werden. berhaupt hneln Funktionen in ihrer Struktur den Prozeduren - ihre Syntax und ihre Konstruktionsregeln sind weitgehend vergleichbar. Sie produzieren jedoch keine Seiteneekte, da sie weder ihre Parameter noch irgendwelche auerhalb der Funktion deklarierten Variablen verndern.

3.3. Datenabstraktion
3.3.1. Kapselung und modulare Programmierung
In den 80er Jahren setzte sich langsam die Erkenntnis durch, da Datenabstraktion genauso wichtig ist wie Prozeabstraktion. Vorlufer der Datenabstraktion war die Idee der Kapselung. Wenn Programme gro werden, also einige tausend Zeilen an Umfang berschreiten, haben Programmierer zwei Probleme: Es wird schwierig, den berblick bzw den Durchblick zu behalten.

20

3.3. Datenabstraktion Das Kompilieren eines so groen Programmes wird zeitaufwendig und damit teuer. Das Zusammenfassen von logisch zusammengehrenden Unterprogrammen und Daten in gekapselten Einheiten (encapsulations ), die unabhngig vom Rest des Programmes kompiliert werden knnen, lst beide Probleme [Seb96]. Man spricht von Datenabstraktion, da hier die Daten im Zentrum des Interesses stehen und die Unterprogramme den Daten zugeordnet werden. Umfangreiche Programme werden schon seit Langem geschrieben, so da sich die Methoden der Kapselung ber einen greren Zeitraum entwickeln konnten. Man spricht auch von der modularen Programmierung, wobei ein Modul irgendeine benannte Programmeinheit ist, die (mehr oder weniger) unabhngig implementiert werden kann. Die Konzepte der modularen Programmierung untersttzen auch das Programmieren im Groen. Sprachen, denen diese Konzepte fehlen (zB frhe Versionen von Pascal ), eignen sich nur sehr bedingt fr die Entwicklung grerer Softwaresysteme [Wat96]. Der Schlssel zur Modularitt liegt in der Abstraktion. Es lassen sich also wieder die beiden Fragen nach dem was und dem wie stellen: Was ist der Zweck eines Moduls? Wie erfllt das Modul seinen Zweck? Fr den Benutzer eines Moduls ist nur das was interessant. Er sieht nur die Schnittstelle (interface) des Moduls, ber die seine Funktionalitt der Auenwelt zur Verfgung gestellt wird. Um diese Funktionalitt anbieten zu knnen, besteht ein Modul typischerweise aus einer Ansammlung von Sprachelementen, die fr einen gemeinsamen Zweck vereinbart werden [Wat96]. Diese Sprachelemente knnen zB Typen, Konstanten, Variablen, Prozeduren oder Funktionen sein. Sie sind gemeinsam fr das wie verantwortlich und sind fr den Benutzer eines Moduls uninteressant. Sie werden daher in dem Modul gekapselt und sind nicht Teil der entlichen Schnittstelle. Das hat den groen Vorteil, da der Anbieter eines Moduls diese Elemente (die Implementierung) nachtrglich ndern kann, ohne da der Benutzer des Moduls etwas davon bemerkt - solange die entliche Schnittstelle unverndert bleibt. Die im weiteren Verlauf des Textes beschriebenen Abstrakten Datentypen (ADT) (siehe Abschnitt 3.3.2), Module (im engeren Sinne), Objekte bzw Klassen (siehe Kapitel 4 auf Seite 29) sowie Pakete (siehe Abschnitt 4.5.3 auf Seite 62) sind allesamt auf Datenabstraktion beruhende Mechanismen zur Modularisierung von Programmen.

3.3.2. Abstrakte Datentypen (ADT)


Das Konzept von abstrakten Datentypen ist recht alt [Seb96]. Selbst in Fortran I sind alle eingebauten Datentypen ADT. Ein gutes Beispiel sind die Fliekommazahlen (oatingpoint types). In fast allen Programmiersprachen kann man Variablen als Fliekommazahlen deklarieren und diese dann mit bestimmten arithmetischen Operationen manipulieren. Das tatschliche Format, in dem eine Fliekommazahl im Speicher abgelegt wird, ist dem Benutzer verborgen. Er darf auch keine neuen Operationen auf diesem Typ

21

3. Abstraktion und Strukturierung denieren. Hier wird also schon ein Kernkonzept der Datenabstraktion angewandt: das Geheimnisprinzip (information hiding). Das Konzept der benutzerdenierten abstrakten Datentypen wurde erst spter entwickelt. Sie weisen die gleichen Eigenschaften auf wie der oben beschriebene eingebaute Typ Fliekommazahl: Eine Typdenition, die es erlaubt, Variablen dieses Types zu deklarieren aber die innere Struktur (Reprsentation) der Variablen vor der Auenwelt verbirgt. Eine Menge von Operationen, mit denen Variablen des Types manipuliert werden knnen. Diese Eigenschaften gehren zu einem Mechanismus zur Konstruktion von Typen in einer Programmiersprache. Dieser Mechanismus mu von dem zugrundeliegenden mathematischen Konzept unterschieden werden, obwohl fr beide manchmal der Begri ADT verwendet wird [Lou03]. Ein neuer abstrakter Datentyp kann zunchst in einer mathematischen Schreibweise, einer sogenannten algebraischen Spezikation oder auch einfach Algebra, als konzeptionelles Modell beschrieben werden. Aus dem Modell wird eine Datenstruktur, wenn der beschriebene Datentyp auf algorithmischer Ebene implementiert wird [GD04]. Dabei wird festgelegt, durch welche bereits existierenden Datentypen die Objekte des Modells reprsentiert werden, und durch welche Algorithmen die Operationen des Modells realisiert werden. Diese Datenstruktur mu dann in einer konkreten Programmiersprache implementiert werden, um einen neuen Mechanismus zur Konstruktion von Typen in dieser Sprache zu erhalten, auf den die obigen beiden Eigenschaften zutreen. Abbildung 3.1 auf der nchsten Seite zeigt ein Beispiel einer Algebra fr einen vereinfachten Datentyp Liste. Um aus dieser Algebra eine Datenstruktur zu entwickeln, mu fr die sogenannten sorts list und elem ein (existierender) Datentyp festgelegt und fr die als mathematische Funktionen beschriebenen Operationen empty, first, rest, append, concat und isempty jeweils ein Algorithmus entwickelt werden. Dabei kann das aus der strukturierten Programmierung bekannte "Top-Down-Verfahren, also die schrittweise Verfeinerung eines zunchst sehr allgemein in einem einzigen Satz beschriebenen Algorithmus, verwendet werden [DD06]. Zur abschlieenden Implementierung mssen Datentypen und Algorithmen in die ausgewhlte Programmiersprache bertragen werden.

3.3.3. ADT vs Objekt


Auf dem beschriebenen Weg - ber ein mathematisches Modell zur Datenstruktur und zur Implementierung eines neuen Datentyps in einer konkreten Programmiersprache kann allerdings genauso gut ein Objekt (bzw eine Klasse) in einer Objekt-orientierten Programmiersprache wie ein ADT in einer imperativen oder funktionalen Programmiersprache entwickelt werden. Auch die Klasse in der Objekt-orientierten Programmierung ist ein Mechanismus zur Konstruktion von benutzerdenierten neuen Typen in einer Programmiersprache. Die beiden fr den Mechanismus ADT genannten Eigenschaften - eine Typdenition, die das Erzeugen neuer Variablen dieses Typs mit gekapseltem Inneren erlaubt, sowie Operationen, mit denen diese Variablen manipuliert werden knnen - gelten

22

3.3. Datenabstraktion algebra sorts ops list1 list, elem {bool ist im folgenden immer implizit dabei} empty : list f irst : list elem append : list list concat : list elem list isempty : list list bool list = { a1 , . . . , an | n 0, ai elem} empty f irst(a1 . . . an ) = rest(a1 . . . an ) = =3 a1 falls n > 0 undeniert sonst

sets functions

a1 . . . an falls n > 0 undef iniert sonst append(a1 . . . an , x) = xa1 . . . an concat(a1 . . . an , b1 . . . bm ) = xa1 . . . an b1 . . . bm isempty (a1 . . . an ) = (n = 0) end list1 Abbildung 3.1.: Beispiel einer Algebra fr einen vereinfachten Datentyp Liste (nach [GD04]) auch fr den Mechanismus Klasse. Dennoch werden beide Mechanismen getrennt behandelt, und eine Objekt-funktionale Sprache wie Scala betritt Neuland, indem sie versucht beide Konzepte zu verschmelzen. Worin liegen also die Unterschiede? Einen przisen Vergleich zwischen ADT und Objekten ndet man bei Cook [Coo90]. Er beschreibt ADT und Objekte als zwei verschiedene Anstze, die nichtsdestoweniger als orthogonale Mglichkeiten zur Spezikation von Datenabstraktionen gesehen werden knnen. Der Zusammenhang zwischen ADT und Objekten wird anhand einer Matrix erlutert, bei der die Zeilen durch (Operationen von) ADT und die Spalten durch Objekte reprsentiert werden. Ausgangspunkt seiner Diskussion abstrakter Daten ist die Unterscheidung zwischen Konstruktoren (abstract constructors): erzeugen oder instanziieren abstrakte Daten. Beobachtungen (abstract observations): gewinnen Information ber abstrakte Daten. Das Verhalten abstrakter Daten lt sich nun durch eine Matrix illustrieren, bei der die Zeilen Beobachtungen und die Spalten Konstruktoren sind und folglich jede Zelle den Wert einer Beobachtung bezglich eines Konstruktors angibt. Am Beispiel der Datenabstraktion fr den Datentyp "Integer-Liste (list) lassen sich Konstruktoren und Beobachtungen sowie die orthogonale Sichtweise von ADT und Objekten auf dieselben Werte gut veranschaulichen (siehe Abbildungen 3.2 bis 3.4 auf der nchsten Seite).

23

3. Abstraktion und Strukturierung

constructor of s observations
null?(s) head(s) tail(s) nil true error error adjoin(s , n) false n s

Abbildung 3.2.: Eine Matrix zur Spezikation von Beobachtungen und Konstruktoren fr Listen (nach [Coo90])

constructor of s observations
null?(s) head(s) tail(s) nil true error error adjoin(s , n) false n s

Abbildung 3.3.: Die ADT-Sicht: Zerlegen der Matrix anhand der Beobachtungen (nach [Coo90])

constructor of s observations
null?(s) head(s) tail(s) nil true error error adjoin(s , n) false n s

Abbildung 3.4.: Die Objekt-Sicht: Zerlegen der Matrix anhand der Konstruktoren (nach [Coo90])

24

3.3. Datenabstraktion In Listing 6.3 auf Seite 103 ndet sich eine Scala-Implementierung von Abbildung 3.3 auf der vorherigen Seite, in Listing 4.1 auf Seite 31 eine Implementierung von Abbildung 3.4 auf der vorherigen Seite. Der abstrakte Datentyp list hat zwei Konstruktoren : nil adjoin nil erzeugt eine leere Liste, adjoin nimmt eine Liste (im Beispiel s) und einen IntegerWert (im Beispiel n) als Argumente entgegen und konstruiert eine neue Liste (im Beispiel s), indem der bergebene Wert n der bergebenen Liste s vorangestellt wird. n ist sozusagen der Kopf (head ) und s der Schwanz (tail ) der neu gebildeten Liste s. Der abstrakte Datentyp list hat drei Beobachtungen : null? head tail null? ist ein Prdikat, das true zurckgibt, wenn sein Argument eine leere Liste ist und sonst false. head gibt den ersten Integer-Wert einer nicht-leeren Liste zurck, tail den Rest. Die Matrix in Abbildung 3.2 auf der vorherigen Seite zeigt folgendes Verhalten der Konstruktoren und Beobachtungen des abstrakten Datentyps list: Fr eine mit dem Konstruktor nil erzeugte leere Liste gibt die Beobachtung null? erwartungsgem true zurck. Die Beobachtungen head und tail erzeugen dagegen einen Fehler (error), da eine leere Liste weder Kopf noch Rumpf besitzt. Fr eine mit dem Konstruktor adjoin erzeugte nicht-leere Liste s gibt die Beobachtung null? erwartungsgem false zurck. Die Beobachtung head gibt den Kopf der Liste (n), die Beobachtung tail den Rumpf der Liste (s) zurck. Ein ADT kann als Zerlegung der Matrix anhand seiner Zeilen betrachtet werden (siehe Abbildung 3.3 auf der vorherigen Seite). Die Datenabstraktion "Integer-Liste wird also aus der Perspektive der Beobachtungen gesehen. Ein Objekt kann als Zerlegung der Matrix anhand seiner Spalten betrachtet werden (siehe Abbildung 3.4 auf der vorherigen Seite). Die Datenabstraktion "Integer-Liste wird also aus der Perspektive der Konstruktoren gesehen.

25

Teil II.

Grundlagen der ObjektOrientierten-Programmierung

4. Programmieren mit Objekten


4.1. Objekt-Orientiertes Software-Engineering
Bei der imperativen Programmierung spielt das Konzept des Zustandes eine zentrale Rolle. Die Zuweisung verschiedener Werte an eine oder mehrere Speicherzellen verndert den Zustand der Variablen, die diese Speicherzellen modelliert. Die Objekt-orientierte Programmierung ist gewissermaen eine Weiterentwicklung der Idee, zustandsbehaftete Objekte der Realwelt (Anwendungsbereich ) mit zustandsbehafteten Variablen eines Programmes (Lsungsbereich ) zu modellieren, wobei sie einfache Variablen durch mehr oder weniger komplexe Programm-Objekte ersetzt. Der Vorteil der Objekt-orientierten Methode liegt darin, da man die gleichen Modellierungsaktivitten fr den Anwendungsbereich wie fr den Lsungsbereich benutzen kann. Zunchst wird der Anwendungsbereich als ein Satz von Objekten und Beziehungen zwischen diesen Objekten modelliert. Dieses Modell reprsentiert die entsprechenden Konzepte in der realen Welt. [...] Als Nchstes wird dann auch der Lsungsbereich als ein Satz von Objekten und Beziehungen zwischen diesen Objekten modelliert. [...] Die wichtigste Eigenschaft bei Objekt-orientierten Methoden ist also, da das Lsungsbereichsmodell gewissermaen eine Transformation des Anwendungsbereichsmodells ist. So gesehen, ist Softwareentwicklung die Beschreibung eines Systems als eine Reihe von Modelltransformationen, die ein Problem in eine Lsung berfhren. (aus Bruegge et al. [BD06]) Diese Einheitlichkeit der Konzepte spielt insbesondere beim Software-Engineering groer und komplexer Projekte (der sogenannten Programmierung im Groen) eine wichtige Rolle und kann als entscheidend fr die Dominanz des Objekt-orientierten Ansatzes in der heutigen Softwareentwicklung (siehe [Dua11]) angesehen werden.

4.2. Daten und Methoden


4.2.1. Prozedurale Datenabstraktion
Das Paradigma der Objekt-orientierten Programmierung beruht auf der Idee der Datenabstraktion (siehe Abschnitt 3.3 auf Seite 20). Es ist ein gutes Beispiel dafr wie Programmiersprachen den Programmierer in zweierlei Hinsicht durch Abstraktion untersttzen knnen [Gro90]:

29

4. Programmieren mit Objekten Die Sprache bietet eine abstrakte Sicht auf die zugrundeliegende Maschine, und ermglicht dem Programmierer so, seine Anweisungen auf komfortablere und ezientere Art zu formulieren. Die Sprache untersttzt den Programmierer dabei Informationen ber die komplexe reale Welt zu abstrahieren und daraus ein vereinfachtes Modell in Form eines Computerprogramms zu erstellen. Die Daten werden in der Objekt-orientierten Programmierung als wichtiger angesehen als die Methoden. Folglich werden die Programme anhand der Daten strukturiert und die Methoden den Daten zugeordnet. Dies ist in gewisser Hinsicht eine Abkehr von der strukturierten (imperativen) Programmierung, die auf die Prozeduren fokussiert ist und groen Wert darauf legt, da Programmtext und Programmablauf einander mglichst hnlich sind [SKS09]. Die Mglichkeit des wilden Hin- und Herspringen im Programmtext mittels goto-Anweisung gibt es in den meisten modernen Programmiersprachen nicht mehr. Das Verteilen der Programmlogik auf eine Vielzahl von Objekten hat aber einen hnlich verwirrenden Eekt und erschwert es einem Programmierer ungemein den berblick ber den Programmablauf zu behalten (siehe Abschnitt 4.7.3 auf Seite 68). Der groe Erfolg der Objekt-orientierten Programmierung zeigt, da dieser Nachteil durch die hervorragende Untersttzung der Entwickler beim Modellieren natrlicher Systeme als Computerprogramme zumindest teilweise ausgeglichen wird.

4.2.2. Objekte als Konstruktoren einer Datenabstraktion


In Abschnitt 3.3 auf Seite 20 wurden abstrakte Datentypen (ADT) und Objekte als zwei orthogonale Formen der Datenabstraktion vorgestellt. Zur Erinnerung wird Abbildung 3.4 auf Seite 24 hier noch einmal dargestellt.

constructor of s observations
null?(s) head(s) tail(s) nil true error error adjoin(s , n) false n s

Abbildung 4.1.: Zerlegung einer Matrix fr eine Datenabstraktion Liste (nach [Coo90]) Abbildung 4.1 zeigt, wie Objekte als Konstruktoren von Datenabstraktionen interpretiert werden knnen. ADT sind dagegen als Zerlegung der Datenabstraktion in eine Reihe von Beobachtungen anzusehen (siehe Abbildung 3.3 auf Seite 24). Die Datenabstraktion, die in den beiden Abbildungen als Beispiel verwendet wird - eine Liste mit zwei Konstruktoren (nil, adjoin(s,n)) und drei Beobachtungen (null?(s), head(s) und tail(s)) - wrde also in zwei Schablonen (Klassen ) zerlegt werden: nil und adjoin(s,n).

30

4.2. Daten und Methoden Die Klassen dienen dann der Erzeugung von Objekten, also Instanzen der Schablonen. Die Argumente der Konstruktoren (s, n) werden zu Instanzvariablen der Objekte, zu ihrem lokalen Zustand. Die Beobachtungen werden zu Feldern (Attributen, Methoden) der Objekte. Jedes Objekt wird durch die Kombination von Beobachtungen reprsentiert, die in Bezug auf dieses Objekt anwendbar sind [Coo90]. Im Beispiel ist auf das Objekt nil nur die Beobachtung null?(s) mit dem vorgegebenen Wert true anwendbar. nil hat keinen lokalen Zustand, ihm werden keine Argumente bergeben. Auf das Objekt adjoin(s,n) sind alle drei Beobachtungen anwendbar, wobei null?(s) den vorgegeben Wert false hat. Die beiden Felder head(s) und tail(s) sind Instanzvariablen des Objektes, sie werden durch die beiden Argumente s (eine Liste) und n (ein Integerwert) bei der Konstruktion des Objektes gesetzt. Sie reprsentieren den lokalen Zustand, in dem sich die aus der gleichen Schablone (Klasse) erzeugten Instanzen (Objekte) unterscheiden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

object listPDA extends Application { class val val val } class val val val NilClass { isNull = true head = null tail = null

AdjoinClass (head: Int, tail: List[Int]) { isNull = false headOfList = head tailOfList = tail

def cons: List[Int] = head :: tail } val nilObject = new NilClass println("nilObjekt is null is " + nilObject.isNull) println("head = " + nilObject.head) println("tail = " + nilObject.tail) val adjoinObject = new AdjoinClass(4, List(3,2,1)) println("adjoinObjekt is null is " + adjoinObject.isNull) println("head = " + adjoinObject.headOfList) println("tail = " + adjoinObject.tailOfList) println("new list = " + adjoinObject.cons) }

Listing 4.1: Die beiden Konstruktoren der Datenabstraktion Integer-Liste als ScalaKlassen

31

4. Programmieren mit Objekten

4.2.3. Klassenparameter und Klassenkonstruktor


Einer Scala-Klasse knnen Parameter bergeben werden, sogenannte Klassenparameter. Diese knnen direkt innerhalb des Klassenrumpfes benutzt werden [OSV08]. Anders als Java-Klassen besitzen Scala-Klassen keinen expliziten Konstruktor. Dabei handelt es sich um ein Sprachelement von Java, das der Erzeugung von Objekten aus einer Klasse dient und vom Programmierer geschrieben werden kann, aber nicht mu (da es einen StandardKonstruktor, den default-constructor gibt). Dieser Konstruktor ist nicht zu verwechseln mit den ebenfalls Konstruktoren genannten Spalten der Matrix der Datenabstraktion "Integer-Liste. Sie sind die Konstruktoren einer Datenabstraktion, nicht einer Klasse. Die Sicht der Objekt-orientierten Programmierung auf die Matrix der Datenabstraktion besteht darin, fr jeden Konstruktor eine Klasse bereitzustellen. Jede dieser Klassen hat dann wiederum einen eigenen Konstruktor, mit dem bei der Erzeugung von Objekten aus der Klasse die Instanzvariablen (der lokale Zustand) gesetzt werden. In Scala mu dieser Klassenkonstruktor nicht vom Programmierer hingeschrieben werden. Vielmehr benutzt der Scala-Compiler die Klassenparameter sowie alle Programmzeilen auerhalb von Feldoder Methodendenitionen einer Klasse, um einen (impliziten) primren Konstruktor (primary constructor) zu erzeugen. Die println() Anweisung in der unten aufgefhrten Klasse ImplicitConstructorExample ist nicht Teil einer Feld- oder Methodendenition. Sie wird daher bei der Erzeugung einer neuen Instanz der Klasse (eines Objektes) mittels new Teil des primren Konstruktors, ebenso wie der bergebene aktuelle Wert des Klassenparameters text.
1 2 3 4 5 6 7

scala> class ImplicitConstructorExample(text: String) { | | println("this is the " + text) | | } defined class ImplicitConstructorExample scala> new ImplicitConstructorExample("actual value") this is the actual value

Wird ein Klassenparameter als val oder var deklariert, wie im folgenden Beispiel, erzeugt der Compiler ein korrespondierendes Feld inklusive Zugrismethoden. Dieser automatisierte Ansatz macht Programmlogik zur Initialisierung von Feldern mit Parameterwerten berssig. Zudem wird der Aufruf einer Klasse dem Aufruf einer Methode hnlicher, wodurch eine konzeptionelle Vereinheitlichung und damit eine Vereinfachung der Sprache erreicht wird.
1 2 3 4 5 6 7 8 9

scala> class SecondaryConstructorExample(val text1: String, val text2: String) { println("primary constructor invoked") println("text1: " + text1 + " text2: " + text2) def this(text1: String) = { this(text1, "text2") println("secondary constructor 1 invoked") }

32

4.2. Daten und Methoden


10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

def this() = { this("text1", "text2") println("secondary constructor 2 invoked") } } defined class SecondaryConstructorExample scala> val ex1 = new SecondaryConstructorExample("arg1", "arg2") primary constructor invoked text1: arg1 text2: arg2 scala> val ex2 = new SecondaryConstructorExample("arg1") primary constructor invoked text1: arg1 text2: text2 secondary constructor 1 invoked scala> val ex3 = new SecondaryConstructorExample() primary constructor invoked text1: text1 text2: text2 secondary constructor 2 invoked

Scala erlaubt die explizite Angabe optionaler sekundrer Konstruktoren (auxiliary constructors ). Diese knnen, wie im obigen Beispiel, dazu dienen das Aufrufen von Klassen mit vielen Parametern zu vereinfachen. Sekundre Konstruktoren mssen (zur Vermeidung von unendlichen Rekursionen und von Redundanzen im Quellcode) stets als erste Anweisung einen vorhergehenden (sekundren oder primren) Konstruktor aufrufen. Sie knnen dabei Klassenparameter mit Standardwerten belegen und somit die Zahl ihrer eigenen Parameter reduzieren. In Scala kann (im Unterschied zu Java) das Schlsselwort super zwar zum Aufruf berschriebener Methoden, nicht jedoch zum Aufruf eines Superklassen-Konstruktors verwendet werden.

4.2.4. Klassen und Objekte


Klassen sind Schablonen fr Objekte. Objekte werden mittels new erzeugt, wobei eventuelle Klassenparameter bergeben werden. Objekte haben (in der Regel) einen Zustand, da sie Instanzvariablen (siehe Abschnitt 4.2.5 auf der nchsten Seite) besitzen, denen Werte zugewiesen werden. Diese Werte knnen sich von Objekt zu Objekt unterscheiden, und sie knnen sich bei einem konkreten Objekt im Laufe seiner Lebensdauer ndern. Das ist so hnlich wie bei den menschlichen Objekten, die alle Instanzen der Klasse homo sapiens sind und sich beispielweise in der Instanzvariablen haarfarbe unterscheiden. Der Zustand eines (modernen) Menschen in Bezug auf seine Haarfarbe kann sich im Laufe seinen Lebens ndern - er kann dieser Instanzvariablen einen neuen Wert zuweisen. Eine abstrakte Klasse wird durch das Schlsselwort abstract gekennzeichnet. Sie kann nicht instanziiert werden, aber andere Klassen knnen von ihr ableiten und damit ihre Eigenschaften erben [OSV08]. Obwohl nicht jede abstrakte Klasse auch abstrakte Elemente (member) haben mu, mu jede Klasse, die abstrakte Elemente hat, als abstract

33

4. Programmieren mit Objekten deklariert werden. Ein abstraktes Element kann eine deklarierte, aber nicht denierte Instanzvariable (zB farbe) oder eine leere Methode ohne Methodenrumpf (zB schnurren) sein 4.3 auf Seite 44. Damit eine Subklasse einer solchen Klassenhierarchie konkret sein kann, mu sie alle abstrakten Elemente ihrer Superklassen implementieren bzw denieren. Um zu vermeiden da es beim berschreiben von geerbten Klassenelemente durch Tippfehler zur versehentlichen Denition neuer Elemente kommt, mu stets das Schlsselwort override vorangestellt werden.

4.2.5. Instanzvariablen
Wie in Abschnitt 4.2.4 auf der vorherigen Seite beschrieben, wird der Zustand eines Objektes durch seine Instanzvariablen beschrieben. Zwei Objekte derselben Klasse knnen sich nur in der Belegung ihrer Instanzvariablen unterscheiden. Diese Belegung kann sich bei einem einzelnem Objekt im Laufe seiner Lebenszeit verndern, so da anhand der Instanzvariablen auch verschiedene Zustnde desselben Objektes im Zeitablauf unterschieden werden knnen. Obwohl Instanzvariablen ein zentrales Konzept in der Objekt-orientierten Programmierung sind, durch das erst die natrliche Modellierung einer Umwelt voller zustandsbehafteter Objekte ermglicht wird, stehen sie gleichzeitig auch fr ein zentrales Problem dieses Programmierparadigmas - den vernderlichen Zustand, und die durch ihn erzeugte, schwer beherrschbare Komplexitt. Da die Zustandsnderungen durch Zuweisungen neuer Werte erfolgen, gilt in Bezug auf Instanzvariablen alles, was in Abschnitt 2.3 auf Seite 10 ber Variablen in imperativen Sprachen und die Zuweisungsoperation gesagt wurde. Object-orientation [...] is essentially an imperative approach to programming. (aus Moseley and Marks [MM06]) Die Idee, den Zustand in Objekten einzuschlieen (encapsulate), ist eine der wesentlichen Strken der Objekt-orientierten Programmierung gegenber dem weniger strukturierten imperativen Programmierstil: In most forms of object-orientated programming (OOP) an object is seen as consisting of some state together with a set of procedures for accessing and manipulating that state. [...] In the OOP context this is referred to as the idea of encapsulation, and it allows the programmer to enforce integrity constraints over an objects state by regulating access to that state through the access procedures (methods). (aus Moseley and Marks [MM06]) Die Strukturierung eines Programmes durch Objekte lst aber nicht das Problem der erhhten Komplexitt aufgrund vernderbarem Zustand im Programm: One problem with this is that, if several of the access procedures access or manipulate the same bit of state, then there may be several places where

34

4.2. Daten und Methoden a given constraint must be enforced (these dierent access procedures may or may not be within the same le depending on the specic language and whether features, such as inheritance, are in use). [...] [...] it is awkward to enforce more complicated constraints involving multiple objects with this approach [...]. (aus Moseley and Marks [MM06]) Daraus folgt, da in einem zustandsbehafteten Objekt-orientierten Programm der Blick auf die Argumente einer Prozedur (bzw Methode) nicht ausreicht, um zu erkennen, welche Auswirkungen das Aufrufen der Prozedur hat: In a stateful program [...] you can never tell what will control the outcome, and potentially have to look at every single piece of code in the entire system to determine this information. (aus Moseley and Marks [MM06]) Zu Beginn dieses Abschnitts wurde erwhnt, da verschiedene Objekte, und verschiedene Zustnde ein und desselben Objektes im Zeitablauf, anhand der Belegung von Instanzvariablen unterschieden werden. Es ist aber tatschlich so, da zwei verschiedene Objekte derselben Klasse trotz exakt gleicher Belegung aller Instanzvariablen unterscheidbare Objekte bleiben. In Bezug auf die Identitt von Objekten gilt es also zwei Konzepte zu unterscheiden [MM06]: Intensionale Identitt (intensional identity) In der Objekt-orientierten Programmierung ist jedes Objekt einzigartig, unabhngig von seinen Attributen (der Belegung seiner Instanzvariablen). Extensionale Identitt (extensional identity) In der relationalen Algebra (siehe Abschnitt 3.3.2 auf Seite 21) werden zwei Dinge als gleich (identisch) betrachtet, wenn sie die gleichen Attribute haben. Die Belegung von Instanzvariablen ist nur fr die extensionale Identitt eines Objektes von Bedeutung. Listing 4.2 veranschaulicht dies anhand der beiden Klassen HelloEq und HelloCase, die beide eine als Klassenparameter bergebene Instanzvariable name haben. Es werden jeweils zwei Instanzen beider Klassen mit dem aktuellen Parameter john erzeugt. Dann wird mit Hilfe der Methode equals auf Wertgleichheit, also extensionale Identitt der Objekte, geprft.
1 2 3 4 5 6 7 8 9 10

class HelloEq(name: String) { println("name: " + name) } abstract class AbstractCase case class HelloCase(name: String) extends AbstractCase { println("name: " + name) } object HelloJohn extends Application {

35

4. Programmieren mit Objekten


11 12 13 14 15 16 17 18 19 20 21 22 23

println("Testing reference equality with eq") println("Without overriding equals in a class, Object#equals is used") println("which degenerates to reference equality (testing with eq)") val x = new HelloEq("john") val y = new HelloEq("john") println("x equals y is " + (x equals y)) println("\nTesting value equality with equals") println("case classes automatically implement equals and hashCode methods") val v = new HelloCase("john") val z = new HelloCase("john") println("v equals z is " + (v equals z)) }

Listing 4.2: Intensionale und extensionale Identitt von Scala-Objekten


Testing reference equality with eq Without overriding equals in a class, Object#equals is used which degenerates to reference equality (testing with eq) name: john name: john x equals y is false Testing value equality with equals case classes automatically implement equals and hashCode methods name: john name: john v equals z is true

1 2 3 4 5 6 7 8 9 10 11 12

Die Ausgabe von Listing 4.2 auf der vorherigen Seite zeigt, da der Test mit equals einmal false und einmal true ergibt. Das liegt daran, da in der Klasse HelloEq die equals Methode nicht berschrieben wurde, und daher in Wirklichkeit (ber den Umweg der Methode Object#equals) mit der Methode eq auf Referenzgleichheit, also intensionale Identitt der Objekte, geprft wurde. Scalas besondere case classes, die erst in Abschnitt 6.3 auf Seite 102 erklrt werden, implementieren hingegen automatisch eine equals Methode, weswegen der Test auf extensionale Identitt im zweiten Fall true ergibt (siehe Abschnitt 4.4.5 auf Seite 54 fr mehr Information zu equals und eq). Intensionale Identitt ist nach Moseley and Marks [MM06] unabdingbar fr das Objekt-orientierte Paradigma, in dem Objekte Abstraktionen mit vernderbarem Zustand darstellen. Zwei Objekte einer Klasse mssen auch dann unterscheidbar bleiben, wenn sie die gleiche Belegung aller Instanzvariablen haben, denn diese Belegung kann sich bei einem oder bei beiden Objekten spter wieder ndern. Es gibt aber auch in der Objekt-orientierten Programmierung Situationen, in denen Zustand nicht vernderbar sein mu - beispielweise wenn ein simpler numerischer Wert reprsentiert werden soll. In solchen Fllen mssen dann besondere Lsungen gefunden werden, um das ursprngliche Konzept der intensionalen Identitt auszuhebeln und durch extensionale Identitt zu ersetzen. Ein Beispiel sind Javas primitive Typen, die in Scala

36

4.2. Daten und Methoden durch die von AnyVal abgeleiteten Objekte reprsentiert werden (siehe Abschnitt 4.5.2 auf Seite 60). Das zustzliche Konzept intensionaler Identitt erhht also die Komplexitt Objektorientierter Systeme, indem es Programmierer dazu zwingt, mental stndig zwischen zwei quivalenten Konzepten (intensionaler und extensionaler Identitt) hin und her zu wechseln. Konfusion zwischen den beiden Konzepten kann nach Moseley and Marks zu schwerwiegenden Programmfehlern fhren [MM06].

4.2.6. Getter und Setter


Als Beispiel fr die Verwendung von Instanzvariablen im Kontext der Objekt-orientierten Programmierung kann die Klasse NilClass in Listing 4.1 auf Seite 31 betrachtet werden. Sie hat nur Instanzvariablen (Daten) als Felder, keine Methoden. Die Daten sind ffentlich (public ), da dies die Voreinstellung in Scala ist. Sie knnen also von berall her angesprochen werden. Diese entlichen Variablen haben in Scala, sofern sie vernderlich sind, eine sehr interessante Eigenschaft [OSV08]. Die Denition
1

var myVar = 3

erzeugt automatisch eine private var myVar sowie je einen Getter und Setter mit derselben Sichtbarkeit wie die denierte Variable (in diesem Fall also public). Die bereits in Abschnitt 4.2.5 auf Seite 34 erwhnte gute Programmierpraxis der Kapselung von Variablen ist in Scala also schon Teil der Sprache selber und braucht nicht mit dem immer gleichen Code vom Programmierer realisiert werden. Die oben denierte Variable myVar kann auerhalb des Objektes, in dem sie deniert ist, nur ber den Getter myVar und den Setter myVar_= angesprochen werden. Dies hat zwei oensichtliche Vorteile: Getter und Setter bilden eine stabile Schnittstelle nach auen. Die Variable selber bleibt fr aufrufenden Code verborgen und kann somit nachtrglich gendert werden. Getter und Setter knnen direkt als Methoden deniert werden, knnen also den Zugri auf die Variable ber Zusicherungen und Bedingungen kontrollieren. Es ist mglich und manchmal sinnvoll, Getter und Setter ohne zugehrige Variable zu denieren. Beispielsweise knnte man in einer Variable celsius die Temperatur in Grad Celsius speichern und ber den Getter celsius und den Setter celsius_= auf diese Variable zugreifen. Um auch Temperaturangaben in Fahrenheit zu ermglichen, braucht keine neue Variable fahrenheit deniert werden. Es gengt die Getter- und Setter-Methoden fahrenheit und fahrenheit_= zu denieren, die auch auf die Variable celsius zugreifen und im Methodenrumpf die Umrechnung zwischen den beiden Maeinheiten vornehmen.

4.2.7. Zugrismodikatoren
Die Sichtbarkeit von Variablen kann mit Zugrismodikatoren (access modiers ) eingeschrnkt werden, wenn die Standardeinstellung public nicht angemessen erscheint:

37

4. Programmieren mit Objekten private nur sichtbar innerhalb der denierenden Klasse protected nur sichtbar innerhalb der denierenden Klasse und ihrer Subklassen Scalas Zugrismodikatoren hneln Javas, unterscheiden sich aber in einigen Details und bieten mehr Flexibilitt (siehe Tabelle 4.1 auf der nchsten Seite). Zugrismodikatoren knnen in Scala mit qualizierenden Parametern versehen werden, die eine Art Obergrenze fr die Sichtbarkeit darstellen. Um die Sichtbarkeit einer Variablen auf ein Paket (package) zu beschrnken, benutzt man beispielsweise private [<nameOfPackage>]. Damit knnen die 4 aus Java bekannten Zugrismodikatoren (siehe Tabelle 4.2 auf Seite 40) angegeben werden, aber natrlich lassen sich mit den qualizierten Zugrismodikatoren von Scala noch dierenziertere Zugrisregeln ausdrcken.

4.2.8. Methoden
Die Klasse AdjoinClass in Listing 4.1 auf Seite 31 hat zustzlich zu ihren Instanzvariablen noch eine Methode cons (vom englischen construct), die aus einem Integerwert und einer Liste eine neue Liste konstruiert und diese an den Aufrufer zurckgibt. Fr Methoden gelten die gleichen Sichtbarkeitsregeln wie fr Variablen, cons ist also auch public und von berall her zugreifbar. Methoden werden mit dem Schlsselwort def eingeleitet. Bemerkenswert an der cons Methode ist der :: Operator, mit dem die neue Liste konstruiert wird. In Scala werden alle Operatoren, die mit einem Doppelpunkt (colon) : enden, entgegen der sonstigen Konvention an den rechten Operanden gebunden. Das heit :: ist eine Listen-Operation und der rechte Operand mu eine Liste sein. Der linke Operand ist dann ein Listenelement, das an den Kopf der Liste angehngt wird. Der Ausdruck head :: tail setzt also den Integerwert head an den Kopf der Integer-Liste tail (im Beispiel wird aus 4 :: List(3,2,1) die neue Liste List(4,3,2,1)). In Scala ist Funktion der Oberbegri fr Methoden, Prozeduren und Funktionen im engeren Sinne. Das heit, Methoden sind Funktionen, die Teil eines Objektes sind, Prozeduren sind Funktionen ohne Rckgabewert (bzw mit Rckgabewert Unit). In Java wird hingegen nur noch der Oberbegri Methode verwendet, der Funktionen und Prozeduren zusammenfat. Dabei entsprechen Methoden mit Rckgabewert void den Prozeduren. Funktionen auerhalb von Klassen sind in Java nicht erlaubt [SB08].

4.3. Komposition und Vererbung


4.3.1. Begriichkeiten
In der Literatur ber Objekt-orientierte Programmierung (zB [DD06]) wird unterschieden zwischen: Objekt-basierter Programmierung mit Klassen, Objekten und Komposition. Objekt-orientierter Programmierung mit den zustzlichen Konzepten Vererbung und Polymorphismus.

38

4.3. Komposition und Vererbung SCALA public Typen[class,trait] berall sichtbar Felder[val,var,def ] berall sichtbar Bemerkung Es gibt kein public Schlsselwort in Scala. Jede Deklaration ohne Zugrismodikator ist entlich.

protected

sichtbar im umgebenden Paket

private

sichtbar im umgebenden Paket

package private scoped private

sichtbar fr den denierenden Typ (inklusive anderer Objekte desselben Typs) und alle abgeleiteten Typen sichtbar fr den denierenden Typ (inklusive anderer Objekte desselben Typs)

scoped protected

private[this]: sichtbar in der Instanz private[Typ]: sichtbar in allen Objekte des Typs private[package]: sichtbar im umgebenden Paket protected[this]: protected[this]: scoped protected sichtbar im um- sichtbar fr den ist quivalent zu gebenden Paket denierenden Typ scoped private (inklusive anderer auer bei Feldern in protected[Typ]: Objekte desselben Vererbungshierarchi protected[package]: Typs) und alle en. sichtbar im umge- abgeleiteten Typen protected[Typ]: benden Paket sichtbar fr den denierenden Typ (inklusive anderer Objekte desselben Typs) und alle abgeleiteten Typen protected[package]: sichtbar im umgebenden Paket sowie in abgeleiteten Typen in anderen Paketen private[this]: sichtbar im umgebenden Paket private[Typ]: private[package]: sichtbar im umgebenden Paket

kann durch scoped private simuliert werden. Scalas private[package] entspricht Javas package private

39

Tabelle 4.1.: Sichtbarkeitsbeschrnkungen durch Zugrismodikatoren in Scala (nach [WP09]).

4. Programmieren mit Objekten

JAVA public

Typen[class,IF] berall sichtbar

Felder[Var,Meth] berall sichtbar

Bemerkung public mu explizit angegeben werden

protected private

sichtbar im umgebenden Paket sichtbar im umgebenden Paket

package private

entlich sichtbar im umgebenden Paket

sichtbar im umgebenden Paket sichtbar fr den denierenden Typ (inklusive anderer Objekte desselben Typs) entlich sichtbar im umgebenden Paket

Javas StandardZugrismodikator, gilt fr alle Deklarationen ohne ModikatorSchlsselwort

scoped private scoped protected

Tabelle 4.2.: Sichtbarkeitsbeschrnkungen durch Zugrismodikatoren in Java (nach [WP09]).

40

4.3. Komposition und Vererbung

4.3.2. Objekt-basierte Programmierung und Komposition


Objekt-basierte Programmierung benutzt Klassen bzw Objekte zur Datenabstraktion und zur Strukturierung eines Programmes. Ein Programm ist demnach aus Objekten aufgebaut, die miteinander kommunizieren. Die Objekte knnen ineinander verschachtelt sein, dh ein benutzerdeniertes Objekt kann nicht nur aus vorgegebenen Objekten der Sprache zusammengesetzt sein, sondern auch aus anderen benutzerdenierten Objekten. Diese sogenannte Komposition dient der Wiederverwendung von Programmcode. Eine Klasse, die mit Variablen vom Typ einer anderen Klasse deklariert wird, braucht deren Funktionalitt nicht zu duplizieren, sondern kann ein Objekt dieser Klasse als Dienstleister benutzen. Die Klasse Dompteur aus Listing 4.3 auf Seite 44 gehrt beispielweise nicht zur Hierarchie der Tierreich-Klassen, obwohl sie im selben Paket liegt. Sie ist eine eigenstndige Klasse, die von keiner anderen Klasse explizit erbt. Implizit erbt sie natrlich, wie alle benutzerdenierten Klassen in Scala, von der eingebauten Klasse ScalaObject und verfgt somit von vorneherein ber eine Grundausstattung an Funktionalitt. Abbildung 4.2 zeigt anhand eines kleinen Ausschnitts aus der Methodenliste fr die im gleichen Listing denierte Klasse Tiger, wie umfangreich diese Grundausstattung an Funktionalitt sein kann.
hatRueckgrat isInstanceOf(TO) istStubentiger ne(x$1: AnyRef notify notifyAll schnurren synchronized[TO] toString wait wait(x$1: Long , x$2: Int) wait(x$1: Long ) !=(x$1: AnyRef ) !=(x$1: Any) $asInstanceOf[TO] Boolean Boolean Boolean Boolean U nit U nit String (x$1 : T O)T O java.lang.String U nit U nit U nit Boolean Boolean ()T O

Abbildung 4.2.: Ausschnitt aus der Methodenliste der Klasse Tiger


Dompteur ist eine Klasse mit zwei Parametern und einer println()-Anweisung auerhalb von Methoden- und Variablendenitionen die in den primren Konstruktor bernommen wird. Einer der Klassenparameter ist vom Typ Tiger. Die Klasse Dompteur benutzt also ein Objekt der Klasse Tiger und dessen Funktionalitt (schnurren). Dies ist ein Beispiel fr Komposition, wenn auch ein nicht ganz konventionelles, da das TigerObjekt aufgrund der straen Syntax von Scala innerhalb der Dompteur-Klasse nicht mehr einer Variablen zugewiesen werden mu. Ob als Klassenparameter bergeben oder explizit als Instanzvariable deniert, ent-

41

4. Programmieren mit Objekten scheidend fr die Kompositionsbeziehung ist, da ein (benutzerdeniertes) Objekt aus anderen (benutzerdenierten) Objekten zusammengesetzt ist und sich deren Funktionalitt bedient.

4.3.3. Objekt-orientierte Programmierung und Vererbung


Obwohl Komposition ein einfacheres Konzept ist, hat es einige gewichtige Vorteile gegenber der Vererbung, dem zentralen, aber in letzter Zeit verstrkt in die Kritik geratenen Konzept der Objekt-orientierten Programmierung. Vererbung ist wie Komposition eigentlich ein von der Natur abgeschautes Konzept. So wie alle mehrzelligen Lebewesen (also auch der Mensch) aus einzelnen Zellen zusammengesetzt (komponiert) sind, erben einzelne Lebewesen, aber auch einzelne Tierarten ihre Eigenschaften von ihren Vorfahren. Das Konzept der Vererbung, wie es in der Informatik verstanden wird, lt sich eher auf die stammesgeschichtliche Entwicklung der Tierarten im Rahmen der Evolution als auf die Vererbung im Rahmen der Fortpanzung zwischen Eltern- und Kindergenerationen bertragen, obwohl viele der verwendeten Begriichkeiten eher aus dem letztgenannten Bereich stammen. Im Laufe der Evolution hat sich im Tierreich eine entwicklungsgeschichtliche Hierarchie herausgebildet, die groe hnlichkeit mit Klassenhierarchien in der Objekt-orientierten Programmierung hat (siehe Abbildung 4.3 auf der nchsten Seite). In der Systematik des Tierreiches besteht zwischen den unterschiedlichen Hierarchieebenen eine is-an (ist ein) - Beziehung. Ein Element einer unteren Ebene ist immer auch ein Element aller seiner bergeordneten Ebenen. Ein Tiger ist eine Grokatze, eine Grokatze ist eine Katze, eine Katze ist ein Fleischfresser, ein Fleischfresser ist ein Sugetier, ein Sugetier ist ein Wirbeltier, und ein Wirbeltier ist ein Tier. Ein Tiger ist also beispielweise auch ein Sugetier und mu insofern alle Eigenschaften eines Sugetieres aufweisen. Auch der Mensch ist ein Sugetier, und Tiger und Mensch haben gemeinsame Eigenschaften, die in der Klasse Sugetiere zusammengefat sind. Fr eine Demonstration der Eigenschaften eines Sugetieres knnte man also sowohl einen Tiger als auch einen Menschen benutzen. Die oensichtlichen Unterschiede zwischen Tiger und Mensch treten erst auf den Hierarchiestufen unterhalb der Klasse der Sugetiere zu Tage (der Mensch ist keine Katze, sondern ein Primat). Durch die Erweiterung der Objekt-basierten Programmierung um das Konzept der Vererbung entstehen Klassenhierarchien, die im besten Fall ganz hnlich aufgebaut sind wie die stammesgeschichtliche Systematik des Tierreiches. Die Modellierung der natrlichen Umwelt des Menschen in einem Computer-Programm wird durch diese Durchgngigkeit der Konzepte wesentlich erleichtert. Das Konzept der Vererbung kann aber die is-an - Beziehung zwischen den verschiedenen Hierarchiestufen nicht erzwingen. Dazu ein Zitat von Steimann, welches sich auf die Anfnge der Objekt-orientierten Programmierung bezieht, also auf die Zeit, in der Smalltalk entstanden ist [SKS09]: Damals war man der Ansicht, einer der Hauptvorteile der objektorientierten Programmierung sei die Wiederverwendung von Code durch Vererbung. Nun hat die Vererbung [...] durchaus etwas mit der auf Generalisierung bzw.

42

4.3. Komposition und Vererbung

Systematik

Beispiele

Reich: Ein Reich umfasst alle Or- Tiere: Das Tierreich umfasst vielzellige Orgaganismen, die grundlegend in hnli- nismen, die ihre Energie aus der Nahrung beziehen. Die meisten besitzen Nerven und Muskeln cher Weise funktionieren. und bewegen sich. Stamm: Ein Stamm umfasst eine Chordagte (Chordatiere): Zum Stamm oder mehrere Klassen und ihre Un- der Chordatiere zhlen Tiere, die in allen Lebensphasen eine Sttzsule (Chorda) in der Lnge tergruppen. des Krpers besitzen. Klasse: Eine Klasse umfasst eine Mammalia: Zur Klasse der Sugetiere zhoder mehrere Ordnungen und ihre len gleichwarme Chordatiere mit Haaren, die ihUntergruppen. re Jungen sugen. Die meisten bringen lebende Junge zur Welt. Ordnung: Eine Ordnung umfasst Carnivora (Fleischfresser): Zur Ordeine oder mehrere Familien und ihre nung der Fleischfresser zhlen Sugetiere, die spezielle Fang- und Reisszhne besitzen. Viele Untergruppen. von ihnen fressen vorwiegend Fleisch. Familie: Eine Familie umfasst ei- Felidae (Katzen): Zur Familie Katzen zhne ode mehrere Gattungen und ihre len Fleischfresser mit kurzem Schdel und gut Untergruppen. entwickelten Krallen. Bei den meisten sind die Krallen einziehbar.
Abbildung 4.3.: Systematik des Tierreiches (nach [Tie10])

43

4. Programmieren mit Objekten Spezialisierung beruhenden Abstraktionshierarchie zu tun: Der allgemeinere Begri (die Superklasse) bertrgt (vererbt) alle seine Eigenschaften auf die spezielleren Begrie (Subklassen), der speziellere erbt sie von den allgemeineren. [...] Problematisch wird es jedoch, sobald man den kausalen Zusammenhang umkehren und von einer mglichen Vererbung auf eine Generalisierung/Spezialisierung schlieen will: Nur weil eine Klasse (zufllig) Eigenschaften einer anderen gebrauchen knnte, heit das noch lange nicht, da die erbende Klasse auch eine Spezialisierung der vererbenden ist. (aus [SKS09]) In der Objekt-orientierten Programmierung trit man daher oft auf Klassenhierarchien, die lediglich aus praktischen Grnden entwickelt wurden, nmlich um bereits vorhandenen Code wiederverwenden und eventuell spezialisieren zu knnen [SKS09]. Beispielsweise knnte in einer Klasse Primat eine Methode gehen() vorhanden sein. Fr einen Programmierer mit dem Auftrag, eine Klasse Haushaltsroboter zu schreiben, liegt es dann nahe, von der Klasse Primat abzuleiten und die Methode gehen() wiederzuverwenden und vielleicht fr die Besonderheiten eines Haushaltsroboters zu spezialisieren. Ein Roboter ist aber kein Primat und gehrt berhaupt nicht dem Tierreich an, auch wenn er einige Eigenschaften menschlicher Primaten besitzt, wie zB die Fhigkeit auf zwei Beinen zu gehen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

object Zirkus extends Application { abstract class Tier { val farbe : String } abstract class Wirbeltier extends Tier { val hatRueckgrat = true } abstract class Saeugetier extends Wirbeltier { val gibtMilch = true } abstract class Carnivor extends Saeugetier { val frisstFleisch = true } abstract class Katze extends Carnivor { protected val istStubentiger : Boolean def schnurren : String } class Tiger extends Katze { // die Anwendung liegt im Paket // package objfunktprogrammierung.allesIstObjekt private[allesIstObjekt] val desTigersSchnurren = "fauchen" override def schnurren : String =

44

4.3. Komposition und Vererbung


27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

desTigersSchnurren override val farbe = "gestreift" override val istStubentiger = false } class SibirischerTiger extends Tiger { override val desTigersSchnurren = "furchterregend fauchen" } class Dompteur(name: String, zirkusTiger: Tiger) { println("Dompteur " + name + " befiehlt dem Tiger: ") def befehlen : String = zirkusTiger.schnurren } val tiger = new Tiger println("Tiger " + tiger.schnurren) println("Tiger sind " + tiger.farbe) println("Ein Tiger hat Rueckgrat ist \"" + tiger.hatRueckgrat + "\"") println("Ein Tiger ist ein Stubentiger ist \"" + tiger.istStubentiger+ "\"")

val dompteur = new Dompteur("Simon", new Tiger) println(dompteur.befehlen) val dompteur2 = new Dompteur("Ramon", new SibirischerTiger) println(dompteur2.befehlen) }

Listing 4.3: Die Systematik des Tierreiches als Scala-Klassenhierarchie Das Listing 4.3 auf der vorherigen Seite zeigt in stark vereinfachter Form, wie die stammesgeschichtliche Systematik des Tierreiches in einem Scala-Programm abgebildet werden kann. Das Programm erzeugt folgendes Ergebnis:
1 2 3 4 5 6 7 8

Tiger fauchen Tiger sind gestreift Ein Tiger hat Rueckgrat ist "true" Ein Tiger ist ein Stubentiger ist "false" Dompteur Simon befiehlt dem Tiger: fauchen Dompteur Ramon befiehlt dem Tiger: furchterregend fauchen

Das Programm demonstriert sowohl Vererbung als auch Komposition. Zwischen den Tierreich-Klassen besteht eine Vererbungshierarchie, ausgedrckt durch das Schlsselwort extend. Die Klasse Tiger hat also 5 (explizite) Superklassen. Sie erbt die Eigenschaften (member) aller ihrer Superklassen, weswegen man einem neuen Tigerobjekt die Fragen tiger.hatRueckrat und tiger.istStubentiger stellen kann, obwohl diese Ei-

45

4. Programmieren mit Objekten genschaften in der Klasse Tiger selber gar nicht deklariert werden. Die Klasse Tiger ist eine konkrete Subklasse ihrer abstrakten Superklasse Katze. Da die Klasse Tiger alle Elemente ihrer Superklassen erbt, enthlt sie zunchst auch die abstrakten Elemente farbe, istStubentiger und schnurren (in Scala mssen, anders als in Java, abstrakte Elemente einer Klasse nicht mit dem Schlsselwort abstract versehen werden - der Compiler erkennt sie von alleine). Sie mu also den beiden vals Werte zuweisen und die Methode implementieren, um zu einer konkreten, instanziierbaren Klasse zu werden. Vererbt werden nur die nicht-privaten Elemente einer Klasse. Die Anweisung
1

private val desTigersSchnurren : String

in einer abstrakten Klasse AbstrakterTiger deniert eine private Variable, die in einer Subklasse wie SibirischerTiger nicht deniert werden bruchte. Sie kann in einer Subklasse allerdings auch nicht berschrieben werden, da sie dort nicht sichtbar ist. Die Anweisung
1 2 3

override val desTigersSchnurren = "furchterregend fauchen" error: value desTigersSchnurren overrides nothing override val desTigersSchnurren = "furchterregendes Fauchen"

in der Klasse SibirischerTiger erzeugt dementsprechend einen Fehler, wenn val desTigers Schnurren in der Superklasse Tiger als privat deklariert wurde. Erweitert man die Sichtbarkeit der Variable auf das ganze Paket (package level), indem man den Zugrismodikator parametrisiert (private[<paketname>]), compiliert das Programm wieder. Im Listing 4.3 auf Seite 44 wurde beispielsweise in der Klasse Tiger der scoped private Zugrismodikator private[allesIstObjekt] verwendet, welcher der Standardeinstellung in Java entspricht (package private, siehe Abschnitt 4.2.7 auf Seite 37).

4.3.4. Parameterlose Methoden


Die Methode
1

def schnurren : String = ...

ist ein Beispiel fr eine parameterlose Methode - eine Methode ohne Klammern hinter dem Methodennamen. In Scala gibt es die Konvention, Methoden ohne Parameter, die nicht oder nur lesend auf vernderbaren Zustand zugreifen (also keine Seiteneekte haben), als parameterlose Methoden zu deklarieren. Der Zugri auf solche Methoden sieht im Programmtext genauso aus wie der Zugri auf eine Variable. Dies ist beabsichtigt, denn es sollte mglich sein eine solche Methode in eine Variable umzuwandeln, ohne da dies Auswirkungen auf aufrufende Programme hat. Methoden, die Seiteneekte haben, aber keine Parameter, werden mit leeren Klammern deklariert
1

def schnurren() : String = ...

46

4.3. Komposition und Vererbung

4.3.5. Polymorphismus und dynamisches Binden


Listing 4.3 auf Seite 44 enthlt auch ein Beispiel fr Polymorphismus und dynamisches Binden, dem neben der Vererbung zweiten wichtigen Charakteristikum Objektorientierter Programmierung. Polymorph bedeutet soviel wie vielgestaltig oder in unterschiedlicher Ausprgung [OSV08]. Im Kontext Objekt-orientierter Programmierung heit dies, da Superklassen stets von all ihren Subklassen ersetzt werden knnen und daher in nicht vorausplanbarer Vielgestaltigkeit daherkommen. Wenn irgendwo in einem Programm ein Objekt vom Typ Tiger bergeben wird, ist zum Zeitpunkt des Programmierens (bzw Kompilierens) oft noch nicht klar, ob das bergebene Objekt tatschlich vom Typ Tiger oder vom Typ einer Subklasse ist (zB SibirischerTiger). Da eine Subklasse die Methoden und Variablen ihrer Superklassen berschreiben kann, ist zum Zeitpunkt des Kompilierens auch noch nicht klar, welche Version einer Methode bei einem Aufruf eigentlich ausgefhrt wird. Dies entscheidet sich erst zur Laufzeit, also wenn whrend des Programmablaufes ein konkretes Objekt mit einer bestimmten Version der Methode aufgerufen wird. Dieses sogenannte dynamische Binden lt sich in Listing 4.3 auf Seite 44 bei folgenden Anweisungen beobachten:
1 2 3 4

val dompteur = new Dompteur("Simon", new Tiger) println(dompteur.befehlen) val dompteur2 = new Dompteur("Ramon", new SibirischerTiger) println(dompteur2.befehlen)

Es wird zweimal auf einem Dompteur-Objekt die Methode befehlen aufgerufen. Dem dompteur wurde ein Tiger, dem dompteur2 eine SibirischerTiger als Klassenparameter bergeben. Der Aufruf derselben Methode auf zwei Objekten vom gleichen Typ fhrt zu unterschiedlichen Ausgaben: fauchen und furchterregend fauchen. Das dynamische Binden ndet aber an anderer Stelle statt, nmlich bei der bergabe der Klassenparameter an die Dompteur-Klasse. Denn berall, wo ein Tiger-Objekt benutzt wird, kann auch ein Objekt einer Subklasse von Tiger benutzt werden (zB SibirischerTiger). Der Methodenaufruf wird zwar an die Methode schnurren der Superklasse gebunden, denn diese ist nicht berschrieben, aber die in der Methode verwendete Instanzvariable desTigersSchnurren ist in der Subklasse berschrieben und erzeugt dadurch eine andere Ausgabe. Der Polymorphismus ist eine elegante Art und Weise, in einem Programm unterschiedliche Flle abzuarbeiten [DD06]. Anstatt diese Flle mit if-else oder (in Java) switch Anweisungen abzufragen, werden sie als Subklassen deniert und das Programm ruft ber das dynamische Binden automatisch die richtige (berschriebene) Methode auf. In Listing 4.3 auf Seite 44 knnte man die Subklasse SibirischerTiger auch weglassen und stattdessen die Klasse Tiger umgestalten (und natrlich den aufrufenden Programmtext anpassen):
1 2 3 4 5

class Tiger(istSibirischerTiger : Boolean) extends Katze { if (istSibirischerTiger) { private val desTigersSchnurren = "furchterregend fauchen" } else { private val desTigersSchnurren = "fauchen"

47

4. Programmieren mit Objekten


6 7 8 9 10 11

} override def schnurren : String = desTigersSchnurren override val farbe = "gestreift" override val istStubentiger = false }

Das ist weniger elegant, erfllt aber denselben Zweck. Nichtsdestoweniger hat diese Lsung einen gravierenden Nachteil - sie ist nicht erweiterbar (extensible). Wenn der Dompteur auch noch einen bengalischen Tiger braucht, der unheimlich faucht, mte ein Programmierer Zugang zu der Klasse Tiger haben, um die if-else Klausel ergnzen zu knnen. Das ist oft nicht der Fall, da in der industriellen Software-Entwicklung Wiederverwendung (reuse) und Modularitt eine groe Rolle spielen und Programme oft auf der Basis schon existierender Software von anderen Anbietern aufgebaut werden. Die Kombination von Vererbung und Polymorphismus ermglicht dagegen den bestehenden Programmtext unverndert zu lassen und einfach eine neue Subklasse BengalischerTiger von der Klasse Tiger abzuleiten, die ebenfalls die Variable desTigersSchnurren berschreibt:
1 2 3

class BengalischerTiger extends Tiger { override val desTigersSchnurren = "unheimlich fauchen" }

Dann knnte vom aufrufenden Programmtext ein Dompteur erzeugt werden, dem ein BengalischerTiger als Klassenparameter bergeben wird, wodurch die Variable desTigersSchnurren automatisch an die passende berschriebene Version unheimlich fauchen gebunden wird:
1

val dompteur = new Dompteur("Simon", new BengalischerTiger)

Die Denition der Klasse Dompteur ist aufgrund des bergebenen Klassenparameters zirkusTiger : Tiger und der von diesem Parameter abhngenden Methode befehlen ein Beispiel fr dynamisches Binden.
1

class Dompteur(name: String, zirkusTiger: Tiger)

Je nachdem von welchem Typ das zur Laufzeit bergebene Tiger-Objekt ist (vom Typ der Superklasse oder einer ihrer Subklassen) erzeugt die Methode eine andere Ausgabe.

4.4. Mehrfachvererbung vs Traits


4.4.1. Das Diamantenproblem
Wenn einfache Vererbung in Verbindung mit Polymorphismus ein mchtiges, wenn auch problembehaftetes Konzept zur Modellierung natrlicher Systeme und zur Wiederverwendung von Programmcode in erweiterbaren Softwaresystemen ist - warum wird dann nicht Mehrfachvererbung (multiple inheritance) als noch mchtigeres Konzept eingesetzt?

48

4.4. Mehrfachvererbung vs Traits Mehrfachvererbung wird in vielen Programmiersprachen vermieden, da es zum berhmten Diamanten-Problem diamond problem kommen kann (siehe Abbildung 4.4). Fahrzeug fortbewegen()

Landfahrzeug override fortbewegen()

Wasserfahrzeug override fortbewegen()

Amphibienfahrzeug

Abbildung 4.4.: Klassenbeispiel fr das Diamanten-Problem bei der Mehrfachvererbung Beim Diamanten-Problem erbt eine Klasse von zwei Klassen, die wiederum eine gemeinsame Superklasse haben [TJJ+ 04]. Die graphische Darstellung einer solchen Vererbungshierarchie hnelt manchmal einem Diamanten - daher der Name. Das Problem liegt dabei in der fehlenden Eindeutigkeit der Vererbungsbeziehung. Wenn die Basisklasse Fahrzeug eine Methode fortbewegen() deniert und diese von den beiden Subklassen der mittleren Ebene (Landfahrzeug und Wasserfahrzeug) berschrieben wird, ist nicht klar, welche Fortbewegungsmethode ein Objekt der Subklasse Amphibienfahrzeug der unteren Ebene hat, da sie von ihren beiden Superklassen und eine gleichnamige Methode mit unterschiedlicher Implementierung erbt.

4.4.2. Javas Interfaces


Java hat eine einfache und sichere, wenn auch nicht ganz so exible Alternative zur Mehrfachvererbung eingefhrt: die Mehrfachimplementierung von Interfaces. Interfaces deklarieren nur eine Schnittstelle. Jede Klasse, die von einem Interface ableitet, mu alle seine deklarierten Methoden implementieren, also (mindestens) die gleiche Schnittstelle anbieten wie das Interface. Wiederverwendung spielt dabei keine Rolle, da Interfaces nur abstrakte (leere) Methoden deklarieren. Die Interface-basierte Programmierung, ursprnglich als Mehrfachvererbung ohne Implementierung entstanden, stellt somit konzeptionell eigentlich etwas ganz anderes dar. Anstatt um Wiederverwendung bereits geschriebenen Codes, um die Komposition von Anwendungen aus fertigen Softwarebausteinen, geht es um die Abstraktion von konkreten Implementierungen.

49

4. Programmieren mit Objekten "Program to an interface, not an implementation" ist ein in der Gemeinschaft der Java-Programmierer hoch angesehener Ratschlag aus dem Design-Pattern Buch der berhmten Gang of Four (GoF) [GHJV95]. hnlich wie Getter- und Setter-Methoden, die von der konkreten Implementierung entlicher Variablen abstrahieren und damit nachtrgliche nderungen ohne Probleme auf Seiten des aufrufenden Codes ermglichen, schaen Javas Interfaces stabile Schnittstellen zur Auenwelt bei gleichzeitiger Flexibilitt bezglich der Implementierungen. Dies ist eine sehr wichtige Errungenschaft bei modularen Systemen, die ber einen lngeren Zeitraum und von verschiedenen Organisationen entwickelt werden.

4.4.3. Scalas Traits


Traits sind Klassen mit besonderen Eigenschaften, die das Zusammenstecken von Anwendungen aus Softwarekomponenten vereinfachen sollen: A trait is a class that is meant to be added to some other class as a mixin. Unlike normal classes, traits cannot have constructor parameters. Furthermore, no constructor arguments are passed to the superclass of the trait. This is not necessary as traits are initialized after the superclass is initialized. (aus [Ode09b]) Folgender leicht vernderter Ausschnitt aus Listing 4.4 auf Seite 53 zeigt, wie Traits deniert und als mixin verwendet werden (siehe Abschnitt 4.4.4 auf der nchsten Seite fr eine Erluterung der Schlsselwrter extends und with):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

class A (constructorParameter: String) { ... } trait B extends A { ... } trait C extends A { ... } class D extends B with C { ... }

Scala bietet mit traits ein wesentlich mchtigeres und exibleres Konstrukt an als das Java interface, welches die Vorteile der Mehrfachvererbung ohne seine Nachteile verspricht. Traits are the fundamental unit of code reuse in Scala. A trait encapsulates method and eld denitions, which can be reused by mixing them into classes. (aus [OSV08])

50

4.4. Mehrfachvererbung vs Traits Aus obigen Zitat wird deutlich, da die Verwendung von Traits eher der Mehrfachvererbung als der Programmierung mit Interfaces entspricht. Zum Einen knnen und sollen Traits voll implementierte Funktionalitt bereitstellen, zum Anderen kann eine Klasse durch das Einbinden (mixin ) mehrerer Traits mit Funktionalitt angereichert werden, so wie bei der Mehrfachvererbung Funktionalitt von mehreren Superklassen geerbt wird.

4.4.4. Linearisierung
Der dabei verwendete Trick zur Vermeidung der Probleme der Mehrfachvererbung nennt sich Linearisierung (linearization). Ein Scala trait ist von der Syntax her einer Scala class ohne Konstruktor-Parameter sehr hnlich, mit Ausnahme des einleitenden Schlsselwortes (siehe Listing 4.4 auf Seite 53 und Listing 4.5 auf Seite 56). Es gibt zwei Mglichkeiten ein Trait in eine Klasse einzubinden: extends Mit diesem Schlsselwort erbt die einbindende Klasse implizit von der Superklasse des eingebundenen Trait. Nur anwendbar, wenn die einbindende Klasse nicht selber explizit von einer Superklasse erbt. with Mit diesem Schlsselwort knnen mehrere Traits in eine Klasse eingebunden werden, auch wenn diese bereits mit dem Schlsselwort extends explizit von einer Superklasse oder implizit von der Superklasse eines Traits erbt. Wenn beispielsweise Klasse ClassB von Klasse ClassA erbt, wird dies mit dem Schlsselwort extends ausgedrckt. Alle Traits werden dann mit dem Schlsselwort with eingebunden:
1 2 3

class ClassB extends ClassA with TraitC { ... }

Wenn Klasse ClassB hingegen von keiner anderen Klasse ableitet, aber mehrere Traits einbindet, wird das erste Trait mit extends, alle weiteren mit with eingebunden.
1 2 3

class ClassB extends TraitC with TraitD with TraitE { ... }

Der Normalfall ist das Einbinden von einem oder mehreren Traits mit dem Schlsselwort with in eine Klasse die bereits explizit von einer Superklasse ableitet (mittels extends). Scalas with ist also analog zu Javas Schlsselwort implement fr Interfaces [WP09]. Wenn ein oder mehrere Traits in eine Klasse eingebunden werden sollen, die noch nicht von einer Superklasse ableitet, mu das erste Trait mit dem Schlsselwort extends eingebunden werden. Die einbindende Klasse leitet aber nicht wirklich von dem mit extends eingebundenem Trait ab, sondern implizit von der Superklasse dieses Traits. In Scala leiten Klassen stets von maximal einer Superklasse mittels extends ab. Traits selber knnen mittels extends von Klassen oder anderen Traits ableiten. Da Traits aber keine

51

4. Programmieren mit Objekten Argumente an den Konstruktor der Superklasse bergeben knnen (siehe Abschnitt 4.4.3 auf Seite 50), kommen dafr nur Klassen mit Konstruktoren ohne Argumente in Frage. Bei einfacher Vererbung ergibt sich eine lineare Vererbungshierarchie. Wenn Traits mit einbezogen werden, die ja von anderen Klassen oder Traits abgeleitet sein knnen, ergibt sich dagegen eine Vererbungshierarchie in Form eines gerichteten, azyklischen Graphen, der linearisiert werden mu: The term linearization refers to the algorithm used to atten this graph for the purposes of resolving method lookup priorities, constructor invocation order, binding of super, etc. (aus [WP09]) Der Algorithmus fr die Linearisierung von Referenztypen wird in Programmierung Scala von Wampler and Payne beschrieben: Linearization Algorithm for Reference Types 1. Put the actual type of the instance as the rst element. 2. Starting with the rightmost parent type and working left, compute the linearization of each type, appending its linearization to the cumulative linearization. (Ignore ScalaObject, AnyRef, and Any for now.) 3. Working from left to right, remove any type if it appears again to the right of the current position. 4. Append ScalaObject, AnyRef, and Any. (aus [WP09]) Wenn mehrere Traits mit with in eine Klasse eingebunden werden, ist daher die Reihenfolge von Bedeutung: die Linearisierung einer Klasse beginnt mit dem zuletzt eingebundenen Trait. Die folgende Klassendenition
1

class Dog extends Animal with Hunter with Sheperd

resultiert also in folgender Linearisierung


1

Sheperd -> Hunter -> Animal -> ScalaObject -> AnyRef -> Any

wobei ScalaObject, AnyRef und Any implizite Superklassen jeder benutzerdenierten Klasse in Scala sind (siehe Abbildung 4.5 auf Seite 61). Jim McBeath beschreibt in seinem Blog ausfhrlich, wie sich die Linearisierung auf die Reihenfolge von Methoden- und Konstruktoraufrufen auswirkt [McB09]. Listing 4.4 auf der nchsten Seite zeigt an einem einfachen, abstrakten Beispiel, da die Reihenfolge von Methodenaufrufen der Linearisierung entspricht, die Reihenfolge der Konstruktoraufrufe entgegengesetzt zur Linearisierung verluft. Dies macht Sinn, da so die SuperklassenKonstruktoren vor denen der Subklassen aufgerufen werden.

52

4.4. Mehrfachvererbung vs Traits

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

object LinearizationExample extends Application { class A { println("A entered") var boolAttribute = true def m = List("A.m") } trait B extends A { println("B entered") var boolAttribute: Boolean override def m = {"B.m" :: super.m} private def setBoolAttribute = if (boolAttribute == true) println("var boolAttribute bleibt: \"" + boolAttribute + "\"") else { boolAttribute = true println("var boolAttribute aendert sich zu: \"" + boolAttribute + "\"") } setBoolAttribute } trait C extends A { println("C entered") var boolAttribute: Boolean override def m = {"C.m" :: super.m} private def setBoolAttribute = if (boolAttribute == true) { boolAttribute = false println("var boolAttribute aendert sich zu: \"" + boolAttribute + "\"") } else println("var boolAttribute bleibt: \"" + boolAttribute + "\"") setBoolAttribute } class D1 extends B with C { override def m = {"D1.m" :: super.m} } class D2 extends C with B { override def m = {"D2.m" :: super.m} }

53

4. Programmieren mit Objekten


50 51 52 53 54 55 56 57 58 59

println("d1:") val d1 = new D1 println("\n" + d1.m + "\n") println("d2:") val d2 = new D2 println("\n" + d2.m + "\n") }

Listing 4.4: Methoden- und Konstruktorenaufrufe in Linearisierungen

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

d1: A entered B entered var boolAttribute bleibt: "true" C entered var boolAttribute aendert sich zu: "false" List(D1.m, C.m, B.m, A.m) d2: A entered C entered var boolAttribute aendert sich zu: "false" B entered var boolAttribute aendert sich zu: "true" List(D2.m, B.m, C.m, A.m)

Die Ausgabe von Listing 4.4 auf der vorherigen Seite macht deutlich, wie der lookup des Aufrufs der Methode m der Reihenfolge der Linearisierung entspricht, also vereinfacht gesagt, von rechts nach links erfolgt, whrend die Konstruktoren beim Erzeugen der beiden Objekte d1 und d2 in umgekehrter Reihenfolge, also von links nach rechts, aufgerufen werden.

4.4.5. Komposition mit Traits


In Scala wird Komposition durch das Zusammenstecken von Softwarekomponenten in Form von Scalas Klassen und Traits realisiert [WP09]. Diese Art der Komposition entspricht weit mehr dem von der Softwareindustrie nachgeeiferten Vorbild der Hardwareindustrie (die ihre Produkte aus genormten, austauschbaren und beinahe beliebig kombinierbaren Komponenten zusammenbaut) als die in Abschnitt 4.3.2 auf Seite 41 beschriebene traditionelle Objekt-orientierte Komposition mit starker Kopplung zwischen den beteiligten Klassen.

54

4.4. Mehrfachvererbung vs Traits Das Listing 4.5 auf der nchsten Seite zeigt drei wichtige Aspekte der Verwendung von Traits in Scala: Nutzung von vordenierten Traits aus der Scala-Bibliothek, hier des Traits Ordered[]. Verwendung abstrakter Variablen und Methoden um eingebundenen Traits Zugri auf den lokalen Zustand der einbindenden Klasse zu ermglichen. Beeinussung des Ergebnisses der Linearisierung durch die Reihenfolge, in der Traits eingebunden werden (wie in Abschnitt 4.4.4 auf Seite 51 beschrieben). Die Klasse Dog erweitert in Zeile 3 das Trait Ordered und demonstriert damit den Einsatz eines wichtigen Traits der Scala-Bibliothek. Um dieses Trait zu verwenden, mssen zwei Dinge beachtet werden: Es mu in eckigen Klammern angegeben werden, was geordnet werden soll. Im Beispiel sind das Objekte der Klasse Dog, weswegen extends Ordered[Dog] geschrieben wird. Mehr Information zu dieser sogenannten funktionalen Typabstraktion ndet sich in Abschnitt 7.3.3 auf Seite 117. Es mu eine Methode compare(that: Dog) implementiert werden, die den Adressaten der Methode compare (this) mit dem bergebenen Argument (that) vergleicht (Zeile 17-19). Die von this und that referenzierten Objekte mssen einen vergleichbaren Typ haben, also beispielsweise beide vom Typ der Klasse Dog sein. Der Vergleich sollte einen Integer mit dem Wert 0 zurckgeben, wenn beide Objekte gleich sind, einen negativen Wert, wenn this kleiner als that ist, und einen positiven Wert im umgekehrten Fall. Dann liefert das Trait Ordered die Funktionalitt fr die Vergleichsoperatoren <, >, <= und >= frei Haus. Das Trait Ordered kann aber aus Grnden, die hier nicht dargestellt werden sollen (aber in [OSV08] nachgelesen werden knnen) nicht eine Implementierung der Methode equals mit dem Operator == ersetzen. Die Denition und Anwendung dieser Methode wird ebenfalls in Listing 4.5 auf der nchsten Seite demonstriert (Zeile 23-27). Bei ihrer Denition sind vier mgliche Fehlerquellen zu beachten: Falsche Signatur verwenden (nicht def equals(other: Any):Boolean). Nur equals und nicht auch hashCode berschreiben (Zeile 20-21, siehe [SB08] fr mehr Informationen). Vernderbare Felder (vars) fr die Denition von equals verwenden. Die Methode equals nicht als quivalenzrelation denieren (siehe [OSV08] fr mehr Information).

55

4. Programmieren mit Objekten Die equals-Methode bzw der mit ihr assoziierte == Operator prfen in Scala die natrliche Gleichheit (natural equality) fr jeden Typ. Das ist die Gleichheit der Werte fr Wertetypen und benutzerdenierte (auf den lokalen Zustand der verglichenen Objekte bezogene) Gleichheit fr Referenztypen, wenn equals wie in Zeile 23-27 berschrieben wird. Die benutzerdenierte Denition fr Gleichheit bezieht sich im Listing 4.5 auf die beiden Instanzvariablen wristHeight und bodyWeight, also auf den lokalen Zustand zweier zu vergleichender Objekte vom Typ Dog:
1

this.wristHeight == that.wristHeight && this.bodyWeight == that.bodyWeight

Wird equals nicht berschrieben, prft der Operator == auch bei Referenztypen standardmig auf Identitt zweier Objekte (wie in Java). Dafr gibt es aber sonst in Scala den Operator eq. Operator == eq Wertetypen gleiche Werte gleiches Objekt Referenztypen gleicher lokaler Zustand gleiches Objekt

Zeile 79 (und Zeile 23 der Ausgabe) von Listing 4.5 zeigen die Anwendung der benutzerdenierten equals Methode auf zwei Dog Objekte. Zeile 80-81 (und Zeile 24-25 der Ausgabe) zeigen, wie das Einbinden des Ordered[] Traits und die Denition einer compare() Methode Vergleiche mit <= und >= ermglichen ohne da diese Operationen im Beispiel explizit deniert wurden.
1 2 3 4 5 6 7 8 9 10

object TraitExample extends Application { class val val var Dog(height: Int, weight: Int) extends Ordered[Dog]{ wristHeight = height bodyWeight = weight smarterThanOtherDog = true

override def toString = "\nIch bin ein Hund mit \n" + height + "cm Schulterhoehe" + " und \n" + weight + "kg Gewicht und smarterThanOtherDog ist \"" + smarterThanOtherDog + "\"" def bark = println("Ich kann bellen") def jump = println("Ich kann " + (3 * height) + "cm hoch springen") def compare(that: Dog) = (this.wristHeight + this.bodyWeight) - (that.wristHeight - that.bodyWeight ) override def hashCode = 41 * (41 + height) + weight

11 12 13 14 15 16 17 18

19 20 21 22

56

4.4. Mehrfachvererbung vs Traits


23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

override def equals(other: Any) = other match { case that: Dog => this.wristHeight == that.wristHeight && this.bodyWeight == that.bodyWeight case _ => false } } trait Shepherd { def bodyWeight: Int var smarterThanOtherDog: Boolean def capture = println("Ich kann Schafe bis zu " + (2 * bodyWeight) + "kg einfangen \n") private def setSmarterThanOtherDog = if (smarterThanOtherDog == true) println("var smarterThanOtherDog bleibt: \"" + smarterThanOtherDog + "\" ") else { smarterThanOtherDog = true println("var smarterThanOtherDog aendert sich zu: \"" + smarterThanOtherDog + "\"") } setSmarterThanOtherDog } trait Hunter { def bodyWeight: Int var smarterThanOtherDog: Boolean def retrieve = println("Ich kann Enten mit bis zu " + (0.5 * bodyWeight) + " kg apportieren") private def setSmarterThanOtherDog = if (smarterThanOtherDog == true) { smarterThanOtherDog = false println("var smarterThanOtherDog aendert sich zu: \"" + smarterThanOtherDog + "\"") } else println("var smarterThanOtherDog bleibt: \"" + smarterThanOtherDog + "\" ") setSmarterThanOtherDog } val huntingSheperd = (new Dog(50, 35) with Shepherd with Hunter)

40 41 42

43 44 45 46 47 48 49 50 51 52 53

54 55 56 57 58

59 60 61

62 63 64 65 66

57

4. Programmieren mit Objekten


67 68 69 70 71 72 73 74 75 76 77 78 79

println(huntingSheperd.toString) huntingSheperd.bark huntingSheperd.jump huntingSheperd.retrieve huntingSheperd.capture val guardingHunter = (new Dog(35, 20) with Hunter with Shepherd) println(guardingHunter.toString) guardingHunter.bark guardingHunter.jump guardingHunter.retrieve guardingHunter.capture println("(huntingSheperd == guardingHunter) ist \"" + (huntingSheperd == guardingHunter) + "\"") println("(huntingSheperd >= guardingHunter) ist \"" + (huntingSheperd >= guardingHunter) + "\"") println("(huntingSheperd <= guardingHunter) ist \"" + (huntingSheperd <= guardingHunter) + "\"") }

80

81

82

Listing 4.5: Wiederverwendung von Programmcode mit Scala traits Listing 4.5 auf Seite 56 erzeugt folgende Ausgabe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

var smarterThanOtherDog bleibt: "true" var smarterThanOtherDog aendert sich zu: "false" Ich bin ein Hund mit 50cm Schulterhoehe und 35kg Gewicht und smarterThanOtherDog ist "false" Ich kann bellen Ich kann 150cm hoch springen Ich kann Enten mit bis zu 17.5 kg apportieren Ich kann Schafe bis zu 70kg einfangen var smarterThanOtherDog aendert sich zu: "false" var smarterThanOtherDog aendert sich zu: "true" Ich bin ein Hund mit 35cm Schulterhoehe und 20kg Gewicht und smarterThanOtherDog ist "true" Ich kann bellen Ich kann 105cm hoch springen Ich kann Enten mit bis zu 10.0 kg apportieren Ich kann Schafe bis zu 40kg einfangen (huntingSheperd == guardingHunter) ist "false" (huntingSheperd >= guardingHunter) ist "true" (huntingSheperd <= guardingHunter) ist "false"

58

4.5. Alles ist ein Objekt Listing 4.5 auf Seite 56 demonstriert auch die Verwendung abstrakter Variablen und Methoden um eingebundenen Traits Zugri auf den lokalen Zustand der einbindenden Klasse zu ermglichen Die beiden Traits des Beispiels, Sheperd und Hunter, deklarieren jeweils die abstrakte parameterlose Methode bodyWeight und die abstrakte boolsche Variable smarterThanOtherDog (Zeile 31-32 und 49-50). Die Klasse des Beispiels, Dog, deniert eine konkrete unvernderliche Variable (val bodyWeight) und eine vernderliche boolsche Variable (var smarterThanOtherDog) und weist ihnen als Klassenparameter bergebene Werte zu (Zeile 5-6). Die Variable smarterThanOtherDog wird als var deklariert, weil ihr in beiden Traits ein neuer Wert zugewiesen wird (Zeile 41 und 57). Die Implementierung der Methode defbodyWeight (Zeile 31 und 49) als val bodyWeight in Zeile 5 der Klasse Dog ist ein Beispiel fr das Prinzip des einheitlichen Zugris (uniform access principle) in Scala [WP09]. In Scala benden sich Methoden und Felder im gleichen Namensraum, es knnen in einer Klasse oder einem Trait also keine gleichnamigen Methoden und Felder nebeneinander existieren. Dafr ist es mglich eine parameterlose Methode (siehe Abschnitt 4.3.4 auf Seite 46) in ein Feld umzuwandeln und umgekehrt, ohne damit aufrufenden Code zu beeintrchtigen. Indem die ein Trait einbindende Klasse die abstrakten Variablen und Methoden des Traits implementiert, ermglicht sie den Methoden des eingebundenen Traits auf den lokalen Zustand von Instanzen der einbindenden Klasse zuzugreifen. Im Beispiel greifen die Trait-Methoden capture (Zeile 34-35), retrieve (Zeile 52-53) sowie setSmarterThanOtherDog (Zeile 37-43 und 55-61) auf die Instanzvariablen bodyWeight und smarterThanOtherDog der einbindenden Dog-Objekte zu. Listing 4.5 auf Seite 56 zeigt auch wie die Linearisierung (siehe Abschnitt 4.4.4 auf Seite 51) der Klassen und Traits durch die Reihenfolge des Einbindens beeinut wird. In Zeile 66 und 72 wird jeweils ein neues Dog Objekt erzeugt. Die Namen der Objekte spiegeln die Reihenfolge wider, in der die beiden Traits eingebunden werden: bei huntingSheperd ist die Reihenfolge with Sheperd with Hunter, bei guardingHunter with Hunter with Sheperd (es sei daran erinnert, da die Linearisierung der eingebundenen Traits von rechts nach links erfolgt). Wenn wie in Listing 4.5 auf Seite 56 Trait Hunter und Trait Animal eine vernderbare Variable (smarterThanOtherDog: Boolean) in der Klasse Dog modizieren, ist die Reihenfolge, in der sie eingebunden werden, ausschlaggebend fr das Ergebnis (true oder false). Dieses ist in den Zeilen 1-2 sowie 12-13 der Ausgabe zu sehen, und entspricht dem, was in Abschnitt 4.4.4 auf Seite 51 ber Konstruktoraufrufe innerhalb von Linearisierungen gesagt wurde.

4.5. Alles ist ein Objekt


4.5.1. Smalltalk als Vorreiter
In rein Objekt-orientierten Sprachen ist jedes Element der Sprache ein Objekt. Smalltalk ist ein Vorreiter dieser Alles ist ein Objekt Idee und wird daher manchmal als de facto Beschreibung der Objekt-orientierten Programmierung bezeichnet [Seb96]. Die Sprache gewinnt viel von ihrer Faszination durch die Beschrnkung auf einige wenige Konzep-

59

4. Programmieren mit Objekten te, die durchgngig und in reiner Form angewandt werden. Dazu gehrt die Idee, da ein Objekt-orientiertes Programm eine Sammlung von Objekten ist, die sich gegenseitig Nachrichten schicken, also ihre Funktionalitt durch den Aufruf eigener Methoden oder der Methoden anderer Objekte erfllen. Diese reine Form der Objekt-orientierung steht im Kontrast zu der weniger konsequenten Umsetzung dieses Konzeptes in eher hybriden Sprachen wie Java, in denen zwischen primitiven Typen und Objekten unterschieden wird und beispielsweise Arrays anders behandelt werden als die sonstigen Kollektionen (collections).

4.5.2. Scalas Klassenhierarchie


Obwohl Scala eine Multiparadigmensprache ist, hat sie das rein Objekt-orientierte Konzept Smalltalks bernommen: in Scala ist jeder Wert ein Objekt und jede Operation ein Methodenaufruf [OSV08]. Abbildung 4.5 auf der nchsten Seite zeigt den Aufbau der Klassenhierarchie von Scala. Ganz oben in der Hierarchie ndet sich die Klasse Any, ganz unten die Klasse Nothing. Das bedeutet, da jede Klasse in Scala eine Unterklasse von Any ist und von dieser Klasse erbt. Nothing ist wiederum eine Unterklasse aller anderen Scala-Klassen, dh jede Scala-Klasse ist eine Superklasse von Nothing. Unterhalb von Any verzweigt sich der Hierarchiebaum in die beiden ste AnyVal fr Wertetypen und AnyRef fr Referenztypen. Der Zweig AnyVal ist nur eine Ebene tief, dh alle Wertetypen leiten direkt von AnyVal ab und haben untereinander keine Vererbungsbeziehung. Die gestrichelten Pfeile in der Abbildung 4.5 auf der nchsten Seite deuten aber darauf hin, welche Wertetypen implizit ineinander umgewandelt werden knnen. Der Zweig AnyRef ist hingegen beliebig tief verschachtelt, denn er stellt den Teil der Hierarchie dar, der durch benutzerdenierte Klassen erweitert wird. Das "mixintrait ScalaObject enthlt vordenierte Methoden, welche die Ausfhrung von ScalaProgrammen ezienter machen sollen [WP09]. Alle Scala-Referenzklassen leiten daher sowohl von AnyRef als auch von ScalaObject ab. Die reibungslose Interaktion mit Java wird dadurch gewhrleistet, da einerseits im Zweig AnyVal fr jeden primitiven Datentyp Javas ein Wertetyp in Scala deniert wird, andererseits alle Referenztypen Javas ebenfalls von AnyRef ableiten (nicht von ScalaObject), so da sie quasi Teil der Scala Klassenhierarchie sind. AnyRef entspricht also der Klasse java.lang.Object. Am unteren Ende des AnyRef Zweiges ndet sich (direkt oberhalb von Nothing) die Klasse Null, die den Typ einer null-Referenz reprsentiert. Der Wert null kann nur Referenztypen, nicht aber Wertetypen zugewiesen werden. Vom Typ Nothing existieren in Scala keine Werte. Er ist trotzdem ntzlich, beispielsweise um die abnormale Termination eines Programmes zu signalisieren [OSV08]. In Scala gibt es sehr viele Mglichkeiten Nichts darzustellen, wie nachfolgendes Zitat von Huang zeigt: NULL: In Java, Object is at the root of the class hierarchy, and in Scala, Any is at the root. Any has two direct subclasses: AnyRef and AnyVal. AnyVal is the superclass for primitives wrapped by the JVM (like Integer, Double, etc),

60

4.5. Alles ist ein Objekt

Any

AnyVal

AnyRef

Double Long Short Byte Unit

Float Int Char Boolean

ScalaObject Scala classes Java Classes

Null

Nothing
Abbildung 4.5.: Scalas Klassenhierarchie (nach [Ode06])

61

4. Programmieren mit Objekten and AnyRef is for all other reference types. The counterpart of Any would be Null, which is at the bottom of the class hierarchy (i.e. Null is the subclass of every class that inherits from AnyRef), and its only instance is null. As a consequence of this, note that Ints cannot be null. NOTHING: Nothing is also at the bottom of the class hierarchy, just like Null. However, unlike Null, Nothing is the subclass of every class, even classes that inherit from AnyVal (and even Null). Also, there are no instances of Nothing. Why is Nothing useful? Glad you asked... NIL: Nil is a singleton that represents the empty List; you can use it to construct Lists like so:
1

1 :: 2 :: 3 :: Nil => List[Int] = List(1, 2, 3)

For this to work, Nil has to be of type List[Nothing] (see? it had a use after all). NONE: None is another singleton representing no value, which you would use when working with monads. [...] one application is eliminating annoying null checks that pop up everywhere in Java, and using the type system to automatically check for them. In that case, None functions like null. UNIT: In Java, a method with no return type was marked as returning void. void was not a class, there were no instances of void, and it just served as a little syntactic marker for methods that didnt return anything. In Scala though, every function has a return type, and if you dont explicitly declare one, it will return the instance () which is of type Unit. (aus [Hua08])

4.5.3. Scalas Pakete


Die Datenabstraktion und die eng damit verbundenen Mechanismen der Modularisierung und Verkapselung gehen bei Scala (wie bei vielen anderen Programmiersprachen) ber die Ebene der abstrakten Datentypen bzw Objekte (Klassen) hinaus und untersttzen auch die "Programmierung im Groen". Insbesondere bei der Arbeit an groen Programmen ist es wichtig, die Kopplung zwischen den Programmteilen zu minimieren [OSV08]. Dies erreicht man durch eine modulare Herangehensweise und eine klare Unterscheidung des Inneren eines Moduls (seiner Implementierung) und seiner Schnittstelle nach auen (Interface). Scala-Code wird in einer Hierarchie von Paketen organisiert, ganz hnlich wie bei Java, nur mchtiger und exibler. Alle von Programmierern denierten Pakete der obersten Ebene werden als Teil des vom System bereitgestellten _root_-Paketes betrachtet. Unterhalb dieser obersten Ebene bilden Scala-Pakete eine geschachtelte Hierarchie. Ein Paket inside, das innerhalb eines Paketes outside deklariert wird (und mit vollem Namen als outside.inside angesprochen wird) ist dabei wirklich Teil des umschlieenden Paketes. In Klassen des inneren Paketes knnen die Elemente des ueren Paketes ohne dessen Prx angesprochen werden. Innere Pakete verdecken gleichnamige uere Pakete. Deren

62

4.6. CLOS Elemente mssen dann ber den voll qualizierten Namen mit Prx angesprochen werden. Bei einer Pakethierarchie outside > outside > inside wrde man also das Prx _root_.outside whlen um aus einer Klasse im Paket inside auf eine Klasse im ueren der beiden Pakete outside zuzugreifen. Um aus einem Paket auf die Funktionalitt eines nicht bergeordneten Paketes (Moduls) zugreifen zu knnen, mu dieses importiert werden. Die import-Anweisung ist in Scala deutlich exibler als in Java. Die drei grundstzlichen Unterschiede sind [OSV08]: import-Anweisungen knnen an jeder beliebigen Stelle im Quellcode auftreten. Es knnen auch Objekte und nicht nur Pakete importiert werden. Es ist mglich einige der importierten Elemente zu verstecken oder umzubenennen. Der zweite Punkt weist auf eine sehr interessante Eigenschaft Scalas hin: die Mglichkeit modularer Programmierung mit Objekten anstatt mit Paketen. Obwohl Scalas Pakete und Importmechanismen schon eine deutliche Verbesserung gegenber dem Vorbild Java darstellen, fehlt die Mglichkeit der Datenabstraktion innerhalb der Pakethierarchie. Ein Paket enthlt immer einen genau festgelegten Inhalt, der nur gendert werden kann, indem der Quellcode gendert wird. Es gibt keine Mglichkeit der (Re-)Kongurierung von Paketen oder der Vererbung zwischen Paketen. Wird hingegen ein Scala-Objekt anstatt eines Paketes als Modul verwendet, dann kann eine Klasse als Schablone fr das Modul genutzt werden - genau wie eine Klasse in der Objekt-orientierten Programmierung als Schablone fr seine Instanzen verwendet wird. Die Klasse beschreibt dann alles, was den mglichen Kongurationen des Moduls gemeinsam ist, dh sie abstrahiert von diesen konkreten Kongurationen. So wie ein Objekt mit einer individuellen Belegung der Instanzvariablen aus einer Klasse instanziiert werden kann, kann ein konkretes Modul in Form eines Objektes mit einer individuellen Konguration aus einem abstrakten Modul in Form einer Klasse abgeleitet werden.

4.6. CLOS
4.6.1. Jenseits der Smalltalk-Klasse
Smalltalk und Scala haben das Konzept der Objekt-Orientierung konsequent umgesetzt, jedes Element beider Sprachen ist ein Objekt. Scala hat die Idee konsequenterweise auch noch auf die Ebene der Pakete ausgedehnt, indem es nicht nur Javas Pakete mit mehr Funktionalitt ausgestattet hat, sondern dem Programmierer auch ermglicht seine Anwendung mit Objekten anstatt mit Paketen zu strukturieren. Damit ist aber noch nicht das Ende der Fahnenstange erreicht. In zumindest zwei Bereichen des Objekt-orientierten Paradigmas wurde bereits lange vor Scala erfolgreich ber das Konzept der Klasse, wie es in Smalltalk implementiert ist, hinausgegangen [GWB91]: Trennung der Konzepte Klasse (Container fr ausfhrbaren Code) und Typ (in ihrer Eigenschaft als benutzerdenierte Sprachelemente, siehe Abschnitt 7.1 auf Seite 111). In Smalltalk sind Typ und Klasse aufgrund ihrer strukturellen hnlichkeit

63

4. Programmieren mit Objekten zu einer Einheit verschmolzen - jede Klasse ist ein Typ und jeder (benutzerdenierte) Typ ist eine Klasse [SKS09]. Eine klare Trennung beider Konzepte (jede Klasse in Scala ist ein Typ, aber nicht jeder Typ ist eine Klasse) ermglicht neue Formen der Abstraktion in der Objekt-orientierten Programmierung (siehe Abschnitt 7.3.5 auf Seite 120) die gewinnbringend beim Aufbau von Komponentensystemen angewandt werden knnen (siehe Abschnitt 8.3.2 auf Seite 128). Trennung der Typhierarchien (siehe Abschnitt 4.5.2 auf Seite 60) von den Methoden-Implementierungen (siehe Abschnitt 4.2.8 auf Seite 38) sowie dynamisches Binden (siehe Abschnitt 4.3.5 auf Seite 47) der Methodenaufrufe anhand aller Methoden-Parameter, nicht nur anhand des ersten Parameters - des Objektes, auf dem die Methode aufgerufen wird. Diese Konzepte wurden bereits in den spten 80ern in CLOS, dem Common Lisp Object System, verwirklicht, und sind bei so jungen Sprachen wie dem erst 2007 entstandenen modernen Lisp Clojure wieder anzutreen [VS10].

4.6.2. Multi-Methoden
In CLOS werden Klassen durch das Makro DEFCLASS und Instanzen durch die Funktion MAKE-INSTANCE erzeugt [DG87]. Klassen haben slots, die den Feldern in Java oder Scala entsprechen. Slots knnen entweder der Klasse oder einer Instanz zugeordnet werden. Sie haben einen Namen und knnen ber die Funktion SLOT-VALUE angesprochen werden. Zustzlich knnen spezielle generische Funktionen deniert werden um die slotWerte auszulesen oder zu berschreiben. Ungewhnlich ist, das Methoden und Klassen in CLOS getrennte Bausteine sind. Methoden gehren also nicht den Klassen, sie werden unabhngig deniert und haben keinen speziellen Zugang zu den slots einer Klasse (zB ber this oder self). Klassen erzeugen daher auch keinen Namensraum fr generische Funktionen oder Methoden. Methoden werden in CLOS zu generischen Funktionen gruppiert. Eine generische Funktion ist ein Objekt, das wie eine Funktion aufgerufen werden kann und mit einer Menge von Methoden assoziiert ist, die den gleichen Namen und die gleichen Argumente haben, von denen aber jede auf einen bestimmten Argumenttyp spezialisiert ist:
1 2 3 4

(defgeneric f (x y)) (defmethod f(x integer) y)) (defmethod f(x integer) (y real)))

Generische Funktionen sind an sich kein neues Konzept. Dennoch stellten sie in ihrer Rolle als wesentlicher Baustein einer Objekt-orientierten Sprache eine Innovation dar. In CLOS schienen generische Funktionen ein natrlicheres konzeptionelles Fundament zu sein als der Nachrichten-Versand la Smalltalk, da: die vorhandenen generischen Funktionen von Common Lisp erweitert werden konnten.

64

4.7. Probleme der Objekt-orientierten Programmierung aufrufender Code weiterhin ausschlielich Funktionsaufrufe verwenden kann. Sowohl den Objekt-orientierten Polymorphismus (siehe Abschnitt 4.3.5 auf Seite 47) als auch generische Funktionen kann man als schrittweise Denition von Operationen auassen. Das bedeutet, da die Operationen nicht zu einem Zeitpunkt an einem Ort fr alle mglichen Flle beschrieben werden, sondern ber die Klassen einer Klassenhierarchie bzw die Methoden einer generischen Funktion verteilt werden und nachtrglich erweiterbar sind. Welches konkrete Stck Programmtext beim Aufruf einer solchen Operationen letztendlich ausgefhrt wird, entscheidet sich erst zur Laufzeit. Im Gegensatz zu den meisten Objekt-orientierten Sprachen beruht diese Entscheidung bei CLOS nicht nur auf dem Typ des ersten Arguments der Operation (dem Empfngerobjekt) sondern auf den Typen aller Argumente. Dies bezeichnet man als multiple dispatch system im Gegensatz zu dem single dispatch system beispielsweise bei Smalltalk. Die Methoden werden multi-methods genannt. Beim Aufruf einer Operation werden in CLOS die folgenden 4 Schritte ausgefhrt: 1. Es wird eine Liste aller Methoden erstellt, die auf die Liste der aktuellen Parameter der aufgerufenen Operation anwendbar sind. 2. Die Liste wird aufrund der Spezitt der Parameter sortiert. 3. Die ausgewhlten Methoden der Liste werden zu einer eektiven Methode kombiniert. Die Art und Weise der Kombination wird in der zugehrigen generischen Methode bestimmt. 4. Die eektive Methode wird mit den Original-Argumenten des Operationsaufrufes ausgefhrt. Die sogenannte Standard Methoden Kombination ist in CLOS immer vorhanden. Sie kombiniert eektive Methoden aus den anwendbaren Primaeren Methoden sowie :before, :after und :around Methoden. Das Lschen oder Hinzufgen einer Methode sowie das ndern der Methodenkombination fhrt zu anderen eektiven Methoden und damit zu einem anderen Programmverhalten beim identischen Aufruf einer Operation. Trotz seiner Originalitt steht CLOS in Bezug auf die schrittweise Denition von Operationen der Objekt-orientierten Programmiertradition am nchsten, obwohl es auch Elemente anderer Stile enthlt. Whrend Smalltalk innerhalb dieser Tradition einen Fokus auf Klassen und Nachrichten hat (also object-centric und message-centric ist), liegt der Fokus bei CLOS auf Klassen und Operationen (object-centric und operation-centric ).

4.7. Probleme der Objekt-orientierten Programmierung


Die Eleganz und Flexibilitt der Objekt-orientierten Programmierung mit Vererbung und Polymorphismus gegenber der nur auf Komposition beruhenden Objekt-basierten Programmierung hat allerdings auch ihren Preis. Steimann zhlt sieben Probleme der Objekt-orientierten Programmierung auf, die wir im Folgenden unter zwei Gesichtspunkten besprechen wollen [SKS09]:

65

4. Programmieren mit Objekten In welchem Zusammenhang stehen sie mit dem von Moseley and Marks identizierten zentralen Problem des Software-Engineering: der vor allem durch vernderlichen Zustand, aber auch durch den Kontrollu und den Codeumfang erzeugten, nicht mehr beherrschbaren Komplexitt [MM06]? Welche Lsungsanstze hat Scala fr diese Probleme anzubieten? Die beschriebenen Probleme sind so gravierend, da neuerdings empfohlen wird, bei der Programmierung mit Objekten im Zweifelsfall Komposition (siehe Abschnitt 4.3.2 auf Seite 41) gegenber Vererbung zu bevorzugen, also von einer zentralen Charakteristik des Objekt-orientierten Ansatzes abzurcken [OSV08]. In der folgenden Abhandlung lie es sich teilweise nicht vermeiden, auf sptere Kapitel dieser Arbeit zu verweisen. Einiges knnte also erst beim zweiten Lesen, nachdem die entsprechenden Kapitel bekannt sind, richtig klar werden.

4.7.1. Das Problem der Substituierbarkeit


Der Begri der Substituierbarkeit wird von Steimann folgendermaen erlutert: "Zuweisungskompatibilitt zwischen verschiedenen Typen bedeutet, da Objekte eines Typs da auftreten drfen, wo Objekte eines anderen Typs erwartet werden. Wenn [...] keine Fehler entstehen, spricht man von der Substituierbarkeit der Objekte des Typen auf der linken Seite der Zuweisung durch die des Typen auf der rechten. (aus [SKS09]) Substituierbarkeit sollte vom Zustand eines Objektes unabhngig sein, also zum Zeitpunkt des Programmierens bzw Kompilierens entschieden werden knnen. Es interessiert im wesentlichen die Frage, ob ein Objekt eines Supertyps, zB einer Superklasse, durch ein Objekt eines Subtypen, einer abgeleiteten Klasse, ersetzt werden kann. Substituierbarkeit kann sicher nicht vorliegen, wenn [...] eine Funktion, die in dem auszutauschenden Typen deniert ist, in dem austauschenden Typ schlicht fehlt. (aus [SKS09]) Weniger klar ist dagegen, wie eng bzw weit funktionale quivalenz, die man bei austauschbaren Typen grundstzlich erwarten sollte, zu denieren ist. Beim subtyping (siehe Abschnitt 7.2 auf Seite 113) macht eine Typeinschrnkung durch den Subtyp die Substituierbarkeit immer fraglich, und fhrt mit groer Wahrscheinlichkeit zu einem Programmfehler zur Laufzeit. Eine nichtkonforme Verhaltensnderung fhrt dagegen zu keinem Fehler, denn der Subtyp stellt das vom Supertyp erwartete Verhalten zur Verfgung, aber in einer nichtkonformen Variante, die zu einem funktionierenden, aber nicht korrekten Programm fhrt. Eine Typerweiterung durch den Subtyp (siehe Abschnitt 7.2 auf Seite 113), zB das Hinzufgen neuer Methoden in einer abgeleiteten Klasse, hat fr sich genommen keine Auswirkungen auf die Substituierbarkeit. Das Problem der Substituierbarkeit hat zunchst einmal nichts mit den von Moseley and Marks identizierten Hauptschuldigen an der permanenten Softwarekrise zu tun:

66

4.7. Probleme der Objekt-orientierten Programmierung dem vernderbaren Zustand, der Kontrolle des Programmusses, und dem Codeumfang [MM06]. Es hat jedoch sehr viel mit Komplexitt zu tun. Diese rhrt in diesem Fall aber von der Einfhrung und Vermischung zustzlicher Konzepte (benutzerdenierte Typen, Klassen/Objekte) und damit verbundener, nicht genau spezizierter Mechanismen (Subtyping, Vererbung) her. Man knnte also den drei oben genannten Komplexittsverursachern noch einen vierten hinzufgen: zustzliche anspruchsvolle Konzepte, in diesem Fall die Objektorientierung und die mit ihr verbundene Datenabstraktion (siehe Abschnitt 3.3 auf Seite 20). Dazu ein Zitat von Moseley and Marks : By data abstraction we basically mean the creation of compound data types and the use of the corresponding compound values (whose internal contents are hidden). We believe that in many cases, unneeded data abstraction actually represents another common (and serious) cause of complexity. (aus [MM06]) Steimann schlgt vor, dem Problem der Substituierbarkeit dadurch abzuhelfen, indem man [...] neben der Sicht [des Programmierers, der] die Typen liefert und [...] sich um deren Substituierbarkeit (und die davon abhngige Subtypenbeziehung) sorgt, auch noch die Sicht [des Programmierers, der] die Typen zu einem konkreten Zweck einsetzen will, einbezieht [...] Diese zweiseitige Sicht auf Typen, nmlich die des Nutzers und die des Anbieters, beginnt sich erst langsam durchzusetzen. (leicht verndert aus [SKS09]) Der Nutzer eines Typs knnte seine Erwartungen an diesen Typ ausdrcken, indem er sie als Rolle speziziert. Scala hat diese zweiseitige Sicht auf Typen aufgegrien, indem es im Rahmen seines mixin Mechanismus den Traits (siehe Abschnitt 4.4.3 auf Seite 50) nicht nur erlaubt, die von ihnen angebotenen Dienste zu spezizieren, sondern auch die von ihnen bentigten Dienste (siehe Abschnitt 8.3.2 auf Seite 128). Der dazu verwendete Mechanismus ist die Objekt-orientierte Typabstraktion im Zusammenspiel mit der selftype Annotation (siehe Abschnitt 7.3.5 auf Seite 120).

4.7.2. Das Fragile-base-class Problem


Dieses Problem beruht laut Steimann auf folgender fundamentaler Schwche der Objektorientierten Programmierung: Zwischen einer Klasse und ihren Subklassen bestehen durch die Vererbung von Eigenschaften starke Abhngigkeiten, die wenn berhaupt nur unvollstndig dokumentiert sind. (aus [SKS09]) Es ist leicht irrefhrend benannt, da es eigentlich auf die Zerbrechlichkeit der Subklassen - nicht der Basisklassen - in einer Vererbungshierarchie hinweist. Nachtrgliche

67

4. Programmieren mit Objekten nderungen an einer Basisklasse knnen den Code ihrer Subklassen zerbrechen. Dabei spielen zwei Dinge eine Rolle [SKS09]: Basisklassen spezizieren in der Regel keinen expliziten vollstndigen Vertrag ihres Verhaltens, auf den sich ableitende Klassen uneingeschrnkt verlassen knnen. Basisklassen kennen ihre (oft nachtrglich programmierten) Subklassen nicht, wissen also nicht, welche Auswirkungen auch kleine und unscheinbare nachtrgliche nderungen auf diese haben. Wie auch beim Problem der Substituierbarkeit geht es hier um die zustzliche Komplexitt durch das Konzept der Objekt-orientierten Datenabstraktion und ihres Vererbungsmechanismus. Obwohl beides eigentlich auf die Wiederverwendung von Code, und damit im Sinne von Moseley and Marks auf die vorteilhafte Verringerung des Codeumfangs abzielt [MM06], werden soviel schlecht spezizierte Abhngigkeiten erzeugt, da die Komplexitt des Softwaresystems erheblich steigt anstatt zu sinken. Auch in diesem Fall ist Scalas mixin Mechanismus eine Antwort auf das Problem (siehe Abschnitt 4.4.5 auf Seite 54 und Abschnitt 8.3 auf Seite 128). Das Einbinden von Traits, also das Zusammenstecken neuer Softwarekomponenten aus bereits bestehenden Komponenten mit Hilfe des Mechanismus der Objekt-orientierten Typabstraktion (siehe Abschnitt 7.3.5 auf Seite 120) verringert die Abhngigkeiten und macht sie gleichzeitig expliziter.

4.7.3. Das Problem der schlechten Tracebarkeit


In der Programmierung ist das Lokalittsprinzip ungemein ntzlich: Dinge, die zusammengehren, stehen im Programmtext beieinander. (aus [SKS09]) Dieser Ansatz wird daher auch von einigen Sprachen, wie beispielsweise dem in Abschnitt 9.3.3 auf Seite 147 beschriebenen PicoLisp, zum Grundprinzip erhoben [Bur06]. Nachdem der "Spaghetti-Code aus der Zeit der sprunghaften Programmierung mit goto durch die strukturierte Programmierung und ihre stets an den Ausgangspunkt zurckkehrenden Unterprogrammaufrufe gebndigt wurde [DD06], fhrte die Objektorientierte Programmierung mit ihren auf viele Klassen verteilten Funktionen und dem dynamischen Binden der Methodenaufrufe zur Laufzeit wieder zu erheblichen Schwierigkeiten beim Verfolgen des Programmablaufes (tracen ) und beim Debuggen [SKS09]. Das Lokalittsprinzip wird [...] durch das dynamische Binden weiter aufgeweicht als durch den Unterprogrammaufruf allein. Dieser Umstand hat dazu gefhrt, da das dynamische Binden von Skeptikern [...] der objektorientierten Programmierung schon als eine Art Goto der 90er Jahre betrachtet wurde. (aus [SKS09])

68

4.7. Probleme der Objekt-orientierten Programmierung Das wilde Hin- und Herspringen im ber viele Klassen verteilten Programmtext kann jeder verfolgen, der ein Objekt-orientiertes Programm debugt. Whrend Moseley and Marks auf die rein deklarative logische Programmierung verweisen, die Komplexitt durch den (annhernd) vollstndigen Verzicht auf die Kontrolle des Programmusses reduziert [MM06], ist der Kontrollu in Objekt-orientierten Programmen oft kaum noch nachvollziehbar. Das dynamische Binden (siehe Abschnitt 4.3.5 auf Seite 47) zur Laufzeit bringt zustzliche Unsicherheit in Bezug auf die Sprungziele der hugen Unterprogrammaufrufe mit sich: Um das Sprungziel und damit die Return-Anweisung, die unmittelbarer Vorgnger war, zu identizieren, mu man die Klasse des Empfngerobjekts kennen, also die Klasse des Wertes der Variable, auf der die Methode aufgerufen wurde. Die ist aber in der Regel nur auf Basis einer vollstndigen Programmanalyse bestimmbar, die sich nicht lokal durchfhren lt. (aus [SKS09]] Da Scala ebenso konsequent Objekt-orientiert ist wie Smalltalk (siehe Abschnitt 4.5.1 auf Seite 59), sind Scala-Programme auch hnlich schwer zu tracen wie beispielsweise Smalltalk-Programme. Dieses Problem liegt sozusagen in der Natur von Scala, und wer es vermeiden will, kann eigentlich nur auf eine rein funktionale Sprache wie zB Clojure umsteigen. Das Problem verschrft sich noch deutlich, wenn parallele bzw nebenluge Programmierung ins Spiel kommt (siehe Abschnitt 8.5.1 auf Seite 133) [MM06]. Hier bietet Scala mit seinem Modell des Methodenversands zwischen actors einen gegenber Java erheblich vereinfachten Kontrollu an (siehe Abschnitt 8.5.2 auf Seite 134). Zudem verfgt Scala natrlich ber einige abstrakte Mechanismen der funktionalen Programmierung zur Vermeidung expliziter Programmschleifen wie zB map oder flatMap, sowie den darauf basierenden mchtigen for Ausdruck (siehe Abschnitt 6.5 auf Seite 106) und kann damit einiges an expliziter Programmsteuerung vermeiden, was das tracen ebenfalls erleichtert.

4.7.4. Das Problem der eindimensionalen Strukturierung


Die Klassenhierarchie einer Software stellt keine Form der hierarchischen Modularisierung dar [SKS09]. Sie ist zwar durch die Vererbungshierarchie strukturiert, die einzelnen Zweige der Vererbungshierarchie sind aber nicht unabhngig voneinander, da durch die Komposition (siehe Abschnitt 4.3.2 auf Seite 41) nicht-hierarchische und eher unorganisierte Beziehungen zwischen den Objekten aller Zweige der Vererbungshierarchie bestehen knnen. Die Mglichkeit, ein greres Programm nach mehreren Kriterien gleichzeitig strukturieren zu knnen, und nicht nur eindimensional durch die Vererbungshierarchie, ist ein langgehegter Wunsch der Softwareindustrie. Die isolierte Betrachtung einzelner Sichten auf das Programm scheint ein Lsungsansatz zu sein, bei dem allerdings der Blick auf das bergeordnete Ganze verloren gehen kann. Moseley and Marks haben in ihrem Bestreben, die Komplexitt in der SoftwareEntwicklung einzudmmen, einen eigenen Ansatz, das sogenannte Functional Relational Programming (FRP), entwickelt (siehe Abschnitt 9.2 auf Seite 140) [MM06]. Dabei

69

4. Programmieren mit Objekten sehen sie das Problem weniger in der nur eindimensionalen Strukturierung Objektorientierter Programme anhand der Vererbungshierarchie, versuchen also nicht, mehr Sichten auf komplexe Objektgeechte zu ermglichen, sondern betrachten die Strukturierung von Programmen anhand von Datenabstraktionen wie Objekten insgesamt als kritisch. Die eine Dimension der Strukturierung in Objekt-orientierten Programmen ist ihrer Meinung nach oft schon eine Dimension zu viel und eine bedeutende Ursache von schdlicher Komplexitt: Subjectivity Firstly the grouping of data items together into larger compound data abstractions is an inherently subjective business [...]. Groupings which make sense for one purpose will inevitably dier from those most natural for other uses, yet the presence of pre-existing data abstractions all too easily leads to inappropriate reuse. Data Hiding Secondly, large and heavily structured data abstractions can seriously erode the benets of referential transparency [siehe Abschnitt 5.2.1 auf Seite 78] [...]. This problem occurs both because data abstractions will often cause un-needed, irrelevant data to be supplied to a function, and because the data which does get used (and hence inuences the result of a function) is hidden at the function call site. This hidden and excessive data leads to problems for testing as well as informal reasoning in ways very similar to state. (aus [MM06]) Moseley and Marks haben aufgrund ihrer kritischen Haltung gegenber der Datenabstraktion an sich bei der Entwicklung des FRP auf das aus der Datenbankwelt bekannte, aber nicht auf diese beschrnkte, relationale Modell (relational model) zurckgegrien: One of the primary strengths of the relational model (inherited by FRP) is that it involves only minimal commitment to any subjective groupings (basically just the structure chosen for the base relations), and this commitment has only minimal impact on the rest of the system. Derived relvars oer a straightforward way for dierent (application-specic) groupings to be used alongside the base groupings. [...] FRP also oers benets in the area of data hiding, simply by discouraging it. Specically, FRP oers no support for nested relations or for creating product types. (aus [MM06]) Eine vergleichbare Abkehr von komplexen Datenabstraktionen ist in Scala nicht zu erkennen. Die Adaption von Smalltalks Alles ist ein Objekt Konzept (siehe Abschnitt 4.5.1 auf Seite 59) ermglicht auch in Scala den Aufbau eindimensional strukturierter Vererbungshierarchien aus beliebig komplexen Objekten. Neu in Scala ist dagegen der mixin Mechanismus zur Komposition von nur sehr lose miteinander verknpften SoftwareKomponenten (siehe Abschnitt 4.4.5 auf Seite 54 und Abschnitt 8.3.2 auf Seite 128). Scala bietet also Mglichkeiten an, die klassische Vererbung zu vermeiden bzw nur noch

70

4.7. Probleme der Objekt-orientierten Programmierung isoliert in einer Komponente anzuwenden, und diese Komponenten dann in einer ModulHierarchie zu organisieren.

4.7.5. Das Problem der mangelnden Kapselung


Steimann beschreibt wie sich die Gefhle bezglich der Qualitten der Objektorientierten Programmierung auf dem wichtigen Gebiet der Kapselung von Information (siehe Abschnitt 4.2.5 auf Seite 34) mit der Zeit gendert haben [SKS09]. Anfnglicher Euphorie ber das neue Konzept folgte die erste Enttuschung, als [...] man merkte, da die ebenfalls gefeierte Vererbung die Kapselung von Klassen auf unangenehme Weise aufbrach [...] (aus [SKS09]) Die endgltige Ernchterung folgte, als das ber Jahre unerkannt gebliebene Problem des Aliasing oenbar wurde: [...] ein [...] Problem, das das gesamte bisherige Bemhen der Objektorientierung um Kapselung auszuhebeln in der Lage ist: das Aliasing-Problem. Wenn nmlich ein Objekt, das ein anderes, vermeintlich kapselndes, in einer seiner Instanzvariablen hlt, einen (weiteren) Alias besitzt, der nicht selbst aus dem vermeintlich kapselnden Objekt stammt, dann ntzt es nichts, wenn diese Instanzvariable unsichtbar und damit unzugreifbar gemacht wird sie wird nmlich gar nicht gebraucht, um auf das (vermeintlich) gekapselte Objekt zuzugreifen. Man bedient sich einfach des Aliases. (aus [SKS09]) Steimann erlutert den Begri des Aliasing folgendermaen: Wenn Variablen keine Objekte enthalten, sondern lediglich auf sie verweisen, wenn sie also Verweissemantik haben, ist es mglich, da mehrere Variablen gleichzeitig dasselbe Objekt benennen. Das nennt man Aliasing. (aus [SKS09]) Es gibt folglich trotz des mglichen Namensschutzes (name protection) fr Instanzvariablen mit Hilfe des private Zugrismodikators (siehe Abschnitt 4.2.7 auf Seite 37) keine Garantie dafr, da das von der privaten Variable referenzierte Objekt nicht noch von einer anderen Variable referenziert wird, also einen Alias besitzt, und somit von auen verndert werden kann. Das Problem des Aliasing beruht oensichtlich auf dem Vorhandensein vernderlichen Zustandes (siehe Abschnitt 2.3 auf Seite 10). Nach Moseley and Marks zerstrt die Anwesenheit von vernderbarem Zustand in Verbindung mit der durch Aliasing durchbrochenen Kapselung dieses Zustandes jede Gewiheit ber das Verhalten eines Softwaresystems [MM06]. Es lohnt, hier noch einmal das zentrale Zitat des Abschnitts 4.2.5 auf Seite 34 zu wiederholen:

71

4. Programmieren mit Objekten In a stateful program [...] you can never tell what will control the outcome, and potentially have to look at every single piece of code in the entire system to determine this information. (aus Moseley and Marks [MM06]) Scala bietet trotz seiner unvernderlichen Variablen (val) keine Mglichkeit, die Probleme des Aliasing zu vermeiden. Die vals garantieren zwar unvernderliche Referenzen der Variablen, die referenzierten Werte bleiben aber vernderbar (siehe Abschnitt 2.2.3 auf Seite 7), und es kann durchaus andere Referenzen auf diese Werte geben, wie folgendes Beispiel zeigt:
1 2 3 4 5 6 7 8 9 10 11 12 13

scala> val myArray = Array(1,2,3) myArray: Array[Int] = Array(1, 2, 3) scala> var myAlias = myArray myAlias: Array[Int] = Array(1, 2, 3) scala> myAlias(1) = 0 scala> myAlias res1: Array[Int] = Array(1, 0, 3) scala> myArray res2: Array[Int] = Array(1, 0, 3)

Bei den vernderlichen Variablen (var) kommt neben den Risiken eines nur scheinbar funktionierenden Geheimnisprinzips (information hiding), wie bei den rein Objektorientierten Sprachen auch, noch das Risiko der vernderlichen Referenzen hinzu.

4.7.6. Das Problem der mangelnden Skalierbarkeit


Die mangelnde Skalierbarkeit Objekt-orientierter Programme wird bei Steimann folgendermaen beschrieben: Die strukturbildende Einheit der objektorientierten Programmierung auf Programmebene ist [...] die Klasse. Grere Einheiten sind innerhalb der gngigsten objektorientierten Programmiersprachen nicht vorgesehen: JAVAs Packages und hnliche Konstrukte sind allenfalls Namensrume und Einheiten der Auslieferung der Status eines Sprachkonstrukts vergleichbar mit Klasse oder Methode kommt ihnen kaum zu. (aus [SKS09]) Auch hier wird, wie beim Problem der eindimensionalen Strukturierung, die gegenstzliche Ausrichtung von Moseley and Marks und den Vertretern der Objekt-orientierten Programmierung deutlich. Whrend die einen bei ihrem Streben nach Einfachheit und geringer Komplexitt jegliche Form der Datenabstraktion vermeiden, sehnen sich die anderen nach besseren und mchtigeren Abstraktionen, die sowohl skalierbar sind als auch eine mehrdimensionale Betrachtung der Programmstruktur ermglichen.

72

4.7. Probleme der Objekt-orientierten Programmierung Bei der Entwicklung von Scala wurde das Problem der mangelnden Skalierbarkeit in mehrfacher Hinsicht adressiert. Zunchst wurden Scalas Pakete mit mehr Mglichkeiten ausgestattet als die Pakete von Java, wobei insbesondere die echte hierarchische Schachtelung von Paketen zu nennen ist. Zudem knnen Scala-Programme mit Objekten anstatt mit Paketen strukturiert werden, also mit vollwertigen Konstrukten der Sprache (siehe Abschnitt 4.5.3 auf Seite 62). Dazu kommt noch Scalas mixin Mechanismus, der es ermglicht aus kleineren Software-Komponenten grere Komponenten zu bilden, die dann auch wieder als mixins noch grerer Komponenten verwendet werden knnen (siehe Abschnitt 4.4.5 auf Seite 54).

4.7.7. Das Problem der mangelnden Eignung


Objekt-orientierte Programmierung ist oensichtlich dort besonders geeignet, wo es gilt Objekte der natrlichen Umwelt mit einem bestimmten Verhalten und vernderlichem Zustand in einem Programm zu modellieren. Dazu bemerkt Steimann : Nicht alle Aufgaben sind aber gleichermaen zur Lsung per objektorientierter Programmierung geschaen. Fr viele logische und Suchprobleme sind beispielsweise funktionale oder logische Programmiersprachen weit besser geeignet; aber auch viele Batch- und Scripting-Probleme (in denen lediglich vorhandene Programme mit den richtigen Daten versorgt und angestoen werden mssen), haben eher imperativprozeduralen denn objektorientierten Charakter. (aus [SKS09]) Die groe Begeisterungsfhigkeit der Programmierergemeinschaft fr neue Trends und Hypes fhrte und fhrt trotz dieser Einsicht dazu, da die Konzepte der Objektorientierung manchmal als von Natur aus gut betrachtet werden, ohne ihre Anwendung zu hinterfragen. Moseley and Marks haben radikal mit dieser Haltung gebrochen, indem sie den vernderlichen Zustand als bse und die Datenabstraktion als problematisch bezeichnen, und somit zwei zentrale Konzepte der Objekt-orientierten Programmierung rundweg ablehnen [MM06]. Scala ist in dieser Hinsicht wesentlich kompromibereiter, denn es bietet dem Programmierer alle Mglichkeiten, imperativ bzw Objekt-orientiert zu programmieren. Dennoch lautet die allgemeine Empfehlung in der Scalawelt, nach einer bergangs- und Lernphase berall dort die Gefahren der Objekt-orientierten Programmierung zu meiden, wo die funktionalen Konstrukte der Sprache eingesetzt werden knnen [OSV08].

73

Teil III.

Grundlagen der Funktionalen Programmierung

5. Konzepte funktionaler Programmierung


5.1. Abgrenzung zur imperativen Programmierung
Imperative Programmiersprachen basieren alle auf der Von-Neumann-Architektur der Computerhardware. Daher sind sich die Sprachen dieses Paradigmas ziemlich hnlich man kann sie sogar als eine einzige kontinuierliche Abfolge von Verbesserungen des Ausgangsmodells FORTRAN I betrachten [Seb96]. Trotz ezienter Ausnutzung der Eigenschaften von Computerhardware und breiter Akzeptanz unter den Programmierern stellt das imperative Paradigma mit seiner Hardware-Nhe aber eine unntige Einschrnkung fr den Proze der Software-Entwicklung dar. Die funktionale Programmierung hat einen ganz anderen Ansatz und orientiert sich an der Mathematik, genauer an der rekursiven Funktionstheorie, und nicht an der Computerhardware [Gro90]. Die rekursive Funktionstheorie wurde etwa zur gleichen Zeit entwickelt wie die Turingmaschine, das der Von-Neumann-Architektur zugrundeliegende mathematische Modell. Eine Turingmaschine kann mit nur drei Operationen (Lesen, Schreiben und Schreib-Lese-Kopf bewegen) alle Probleme lsen, die auch von einem Computer gelst werden knnen. Dabei spielen neben Ausdrcken (berechne etwas) Anweisungen an den Computer (tue etwas) eine wichtige Rolle. Eine der fundamentalen Einsichten der rekursiven Funktionstheorie und damit der funktionalen Programmierung ist die Erkenntnis, da auf Anweisungen verzichtet werden kann - alles kann mit Ausdrcken erreicht werden. Da die Unterscheidung zwischen Anweisung (Befehl, Kommando) und Ausdruck bei der Abgrenzung zwischen imperativen und funktionalen Sprachen eine so groe Rolle spielt, sollen beide Elemente der Programmierung hier zunchst deniert werden (nach [Wat96]): Anweisung (Befehl, Statement) Ein Befehl ist ein Programmstck, das ausgefhrt wird, um Variablen zu berschreiben. Es gibt einfache, aber auch zusammengesetzte Befehle. Wesentliche Befehle imperativer Programmiersprachen sind: berspringen (skip): if E then C ist Abkrzung fr if E then C else skip Zuweisungen Prozeduraufruf sequentielle Befehle bedingte Befehle Wiederholungsbefehle

77

5. Konzepte funktionaler Programmierung Diese Befehle haben alle einen Kontrollu mit einem Eingang und einem Ausgang und entsprechen damit den Prinzipien der strukturierten (imperativen) Programmierung. Viele Sprachen stellen aber auch noch Sprnge, Auswege und Ausnahmen zur Verfgung um die Kontrollstrukturen mit mehreren Ausgngen versehen zu knnen und damit exibler zu machen. Ausdruck (Expression) Ein Ausdruck ist ein Programmstck, das ausgewertet wird um einen Wert zu liefern. Ausdrcke knnen auf verschiedene Weise zusammengesetzt werden. Grundlegende Arten von Ausdrcken sind: Literal: fester und manifester Wert eines Typen, zB 365 oder string Aggregat: baut zusammengesetzten Wert aus den Werten seiner Komponenten auf, zB das Tupel
1 2 3

scala> (1, "hello", Console) res0: (Int, java.lang.String, object Console) = (1,hello,scala.Console\$@16c1227)

Funktionsaufruf Bedingter Ausdruck (hnelt einem bedingten Befehl) Zugrie auf Konstanten und Variablen Watt beschreibt den Unterschied zwischen funktionalem und imperativem Programmieren folgendermaen: Funktionales Programmieren ist gekennzeichnet durch den Gebrauch von Ausdrcken und Funktionen. Imperatives Programmieren ist gekennzeichnet durch den Gebrauch von Variablen, Befehlen und Prozeduren. Natrlich haben auch imperative Sprachen Ausdrcke und Funktionen, aber oft sind diese Teile der Sprache verkmmert. (aus [Wat96]) So wie funktionale Konzepte Teil der imperativen (und der aus ihnen hervorgegangenen Objekt-orientierten) Sprachen sind, haben auch viele funktionale Sprachen imperative Bestandteile akquiriert. Dadurch haben sie an konzeptioneller Reinheit verloren, jedoch manchmal an Ausfhrungsgeschwindigkeit und Praktikabilitt in der alltglichen Programmierung gewonnen.

5.2. Ausgangspunkt Mathematik


5.2.1. Mathematische Funktionen
Mathematische Funktionen sind Abbildungen aus einer Ursprungsmenge in eine Bildmenge [Seb96]. Die Funktion speziziert dabei beide Mengen (explizit oder implizit) sowie die Abbildung (in Form eines Ausdrucks). Eine Funktion kann auf ein bestimmtes Element

78

5.2. Ausgangspunkt Mathematik der Ursprungsmenge angewandt werden und gibt ein Element der Bildmenge zurck. Eine der fundamentalen Charakteristiken von mathematischen Funktionen ist die Kontrolle der Auswertungsreihenfolge von Ausdrcken einer Abbildung durch: Rekursion (siehe Abschnitt 5.2.2 auf Seite 81) Bedingte Ausdrcke Bei imperativen Sprachen wird die Auswertungsreihenfolge der Anweisungen hingegen durch sequentielle Abarbeitung und iterative Wiederholung bestimmt. Eine weitere wichtige Eigenschaft mathematischer Funktionen ist das Fehlen von Seiteneekten. Fr eine gegebene Menge von Argumenten wird eine Funktion immer das gleiche Ergebnis zurckgeben. Auer der Rckgabe des Ergebniswertes hat der Aufruf einer Funktion also keine weiteren (vielleicht schwer kontrollierbaren) Auswirkungen innerhalb eines Programmes. Diese Eigenschaft nennt man Referentielle Transparenz. Sie macht (groe) Programme in funktionalen Sprachen fr die Programmierer viel einfacher zu durchschauen und zu kontrollieren als in imperativen Sprachen. Ein einfaches Beispiel fr eine Funktionsdenition in Scala sieht folgendermaen aus:
1 2

scala> def square(x: Int): Int = x * x square: (x: Int)Int

Der Funktionsname wird gefolgt von einer Parameterliste in Klammern und dem die Abbildung beschreibenden Ausdruck. In einer statisch typisierten Sprache wie Scala (siehe Abschnitt 7 auf Seite 111) kommen noch Typannotationen fr die Funktionsparameter und den Rckgabewert hinzu (im Beispiel Int fr Integer). Komplexe Funktionen werden mit Hilfe anderer Funktionen deniert. Eine Funktion hherer Ordnung nimmt entweder eine andere Funktion als Parameter oder gibt eine andere Funktion als Ergebnis zurck. Wichtige Beispiele fr Funktionen hherer Ordnung sind (nach [Seb96]): Komposition Eine Funktion mit zwei Funktionsparametern, die eine Funktion zurckgibt, deren Wert die Anwendung der ersten Funktion auf das Resultat der zweiten Funktion ist.
1 2 3

f(x) = x + 2 g(x) = 3 * x h(x) = f(g(x)) oder h(x) = (3 * x) + 2

Komposition ist nicht auf zwei Funktionen beschrnkt, es knnen beliebig viele Funktionen beteiligt sein. Konstruktion Eine Funktion, der eine Liste von Funktionen als Parameter bergeben wird, jede dieser Funktionen auf ein Argument anwendet und die Ergebnisse in einer Rckgabeliste sammelt.
1 2

g(x) = x * x h(x) = 2 * x

79

5. Konzepte funktionaler Programmierung


3 4

i(x) = x / 2 [g,h,i](4) ergibt (16,8,2)

Apply-to-all Eine Funktion, die eine einzelne Funktion als Parameter empfngt, sie auf jeden Wert in einer Argumentenliste anwendet und die Ergebnisse in einer Rckgabeliste sammelt.
1 2

h(x) = x * x a(h,(2,3,4)) ergibt (4,9,16)

Funktionale Programmierung versucht die Eigenschaften mathematischer Funktionen weitestmglich zu kopieren. Rein funktionale Sprachen benutzen keine Variablen und keine Zuweisungsoperationen. Ein Programmierer mu sich also nicht mehr mit dem Verwalten von Speicherzellen beschftigen. Ohne Variablen ist aber auch keine Iteration mglich, denn diese wird (zB in while oder for Anweisungen) ber Variablen gesteuert. Stattdessen wird Wiederholung durch Rekursion realisiert, dem Thema von Abschnitt 5.2.2 auf der nchsten Seite. Es ist diese Orientierung an der Mathematik, die die funktionale Programmierung heute wieder in den Blickpunkt der Softwareindustrie und der Forschung rckt. Moseley and Marks erlutern die daraus resultierenden komplexittsverringernden Eigenschaften dieses Paradigmas folgendermaen: The primary strength of functional programming is that by avoiding state (and side-eects) the entire system gains the property of referential transparency which implies that when supplied with a given set of arguments a function will always return exactly the same result (speaking loosely we could say that it will always behave in the same way). Everything which can possibly aect the result in any way is always immediately visible in the actual parameters. It is this cast iron guarantee of referential transparency that obliterates one of the two crucial weaknesses of testing as discussed above [a test in one state tells you nothing at all about the system in a dierent state]. As a result, even though the other weakness of testing remains (testing for one set of inputs says nothing at all about behaviour with another set of inputs), testing does become far more eective if a system has been developed in a functional style. By avoiding state functional programming also avoids all of the other staterelated weaknesses [...], so for example informal reasoning also becomes much more eective. (aus [MM06]) Die reine funktionale Programmierung ist also das Mittel zur Bekmpfung der unbewltigten Softwarekrise, indem sie deren Hauptursache ausschaltet: Komplexitt durch vernderbaren Zustand. Moseley and Marks haben dementsprechend die Logik-Komponente ihres in Abschnitt 9.2 auf Seite 140 vorgestellten, neuartigen Ansatzes zur Programmierung, dem Functional Relational Programming (FRP), auf reinen Funktionen aufgebaut.

80

5.2. Ausgangspunkt Mathematik

5.2.2. Rekursion
Obwohl unter Programmierern der Spruch Rekursiv geht meistens schief nicht ganz unbekannt ist, handelt es sich bei der Rekursion um ein mchtiges Konzept, das insbesondere in der funktionalen Programmierung eine zentrale Rolle spielt. Fr viele Menschen ohne besondere mathematische Begabung ist die rekursive Denkweise anfnglich sicher schwerer zugnglich als die iterative Denkweise. Sie ist aber fr viele algorithmische Probleme natrlicher und einfacher und ist ganz wesentlich fr die Vorteile der funktionalen Programmierung gegenber dem imperativen Paradigma verantwortlich. Eine rekursive Funktion zeichnet sich dadurch aus, da die Funktion sich selber (mit genderten Parametern) im Funktionsrumpf aufruft. Bei der folgenden Implementierung des Fakultts-Algorithmus in Scala erkennt man, wie die Funktion sich solange mit dem Parameter n-1 selber aufruft, bis n == 0 gilt (aus [Ode09a]). Danach werden dann die ganzen Aufrufe rckwrts wieder abgearbeitet und dabei das Ergebnis berechnet. Das alles geschieht ohne Variablen, in denen Zwischenergebnisse abgespeichert werden.
1

def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n-1)

Diese Funktion erfllt die an sie gestellte Aufgabe, dh sie berechnet den korrekten Ergebniswert. Sie hat aber einen Schwachpunkt, der sie inezient macht: der rekursive Aufruf der Funktion factorial(n-1) ist nicht die letzte Aktion beim Aufruf der Funktion factorial(n). Er wird noch gefolgt von einer Multiplikation mit n. Die Funktion ist daher nicht tail recursive (End-rekursiv) und kann vom Compiler nicht in eine besonders eziente Reprsentation umgewandelt werden. Unter Ausnutzung von Scalas verschachtelten Funktionen kann die Fakulttsfunktion so angepat werden, da die letzte Aktion der rekursive Aufruf der Funktion ist (nach [Fol07]) . Die Funktion wird dadurch tail rekursive und der Compiler kann sie wesentlich ezienter umsetzen.
1 2 3 4 5 6 7

def fact(n: Int) = { def fact1(n: Int, acc: Int): Int = { if (n == 0) acc else fact1(n - 1, n * acc) } fact1(n, 1) }

Bei der Entwicklung rekursiver Algorithmen in Scala sollte stets versucht werden, die Implementierung End-rekursiv zu machen. Man gibt dadurch dem Compiler ganz andere Mglichkeiten, den Code zu optimieren.

5.2.3. Lambda-Notation
Lambda-Ausdrcke haben als wichtiges und mchtiges Konzept funktionaler Sprachen jahrzehntelang in relativer Obskuritt berdauert, bis sie durch die Tatsache populr wurden, in der Objekt-orientierten Sprache Java nicht enthalten zu sein. In der Funktionstheorie wird zwischen der Denition und der Benennung einer Funktion unterschieden

81

5. Konzepte funktionaler Programmierung [Seb96]. Die Lambda-Notation ermglicht es namenlose Funktionen zu denieren. Der folgende Lambda-Ausdruck speziziert einen Parameter x und die durch die Funktion denierte Abbildung x*x*x:
1

lambda(x) x*x*x

Der Parameter eines Lambda-Ausdrucks kann als gebundene Variable bezeichnet werden. Vor der Evaluierung des Ausdrucks x*x*x reprsentiert x alle mglichen Werte der Ursprungsmenge der Funktion. Fr die Evaluierung wird x an einen speziellen Wert gebunden, zB 2,
1

lambda(x)(x*x*x)(2)

was in dem Wert 8 resultiert. Lambda-Ausdrcke gestatten die vollstndige Auswertung separater Teilausdrcke. Sie werden beispielsweise bei Callback-Funktionen eingesetzt oder berall dort, wo eine Funktion nur einmal verwendet wird, und es daher berssig erscheint, sie zu benennen.

5.2.4. Currying
Currying ist eine nach dem Logiker Haskell B. Curry benannte Technik der funktionalen Programmierung [Lou03]. Dabei wird aus einer Funktion mit einer Parameterliste eine Funktion mit mehreren Parameterlisten, indem jeder Parameter der ersten Liste als eigene Liste bergeben wird [OSV08]. Beim Aufruf einer curried function erhlt man eigentlich zwei traditionelle Funktionsaufrufe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

scala> def product(x: Int, y: Int) = x * y product: (x: Int,y: Int)Int scala> product(2,3) res0: Int = 6 scala> def curriedProduct(x: Int)(y: Int) = x * y curriedProduct: (x: Int)(y: Int)Int scala> curriedProduct(2)(3) res3: Int = 6 scala> val twoTimes = curriedProduct(2)_ twoTimes: (Int) => Int = <function1> scala> twoTimes(3) res4: Int = 6

Der erste Funktionsaufruf nimmt (im Beispiel) den Parameter x entgegen und gibt einen Funktionswert fr die zweite Funktion zurck. Anstelle eines Ergebniswertes wird also zunchst eine Funktion (ein closure, siehe Abschnitt 6.1 auf Seite 95) zurckgegeben.

82

5.2. Ausgangspunkt Mathematik Diese wird dann mit dem zweiten Parameter y aufgerufen und berechnet das Ergebnis. Currying ermglicht es eine Funktion mit nur einer ihrer Parameterlisten aufzurufen und die zweite Parameterliste durch einen Platzhalter zu ersetzen (in Scala ist der Unterstrich (underscore) der vielseitig verwendbare Platzhalter). Das Ergebnis ist (im Beispiel) eine Referenz auf eine Funktion twoTimes, die ihr einziges Argument 3 mit 2 multipliziert. In Scala kann Currying auch auf bereits existierende Funktionen angewandt werden. Dazu gibt es die im Function Singleton denierte Methode curried (und die inverse Methode uncurried).
1 2

Function.curried(<existierendeFunktion>) Function.uncurried(<existierendeFunktion>)

Ein oensichtliches Anwendungsgebiet fr Currying ist die Simulation von Funktionen mit mehreren Argumenten in funktionalen Programmiersprachen wie Haskell oder ML, die nur ein Funktionsargument erlauben [Spi08a]. In Sprachen wie Scala, auf die diese Einschrnkung nicht zutrit, kann Currying dazu benutzt werden, neue Kontrollstrukturen zu schreiben, die wie Bestandteile der Sprache erscheinen [OSV08]. Syska und Wilhelmi haben sich mit genau diesem Thema, der Kontrollabstraktion bzw der Entwicklung neuer, eigener Kontrollstrukturen, beschftigt, und es anhand der Entwicklung einer eigenen while Schleife anschaulich erklrt [SW10]. Die eigene myWhile Schleife soll syntaktisch und semantisch Scalas eingebauter while Schleife entsprechen, abgesehen vom neuen Bezeichner myWhile:
1 2 3 4 5

var x = 0 myWhile (x < 10) { println(x) x=x+1 }

Die Vorgehensweise von Syska und Wilhelmi bei der Entwicklung von myWhile sieht folgendermaen aus: Eigene myWhile-Schleife aber wie? 1. Was wird bentigt? Methodenname Bedingung, die berprft wird Methodenrumpf, der ausgefhrt werden kann 2. Welche Mglichkeiten sind geeignet? rst-class & higher-order Funktionen by-name-parameter currying (aus [SW10])

83

5. Konzepte funktionaler Programmierung Das folgende Beispiel zeigt, wie die drei genannten Mglichkeiten von Scala bei der Denition von myWhile eingesetzt werden:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

scala> def myWhile(condition: => Boolean , operation: => Unit ){ | operation | if(condition){ myWhile(condition, operation)}} myWhile: (condition: => Boolean,operation: => Unit)Unit scala> var x = 0 x: Int = 0 scala> myWhile(x < 10) { | println(x) | x = x + 1 | } <console>:8: error: not enough arguments for method myWhile: (condition: => Boolean,operation: => Unit)Unit. Unspecified value parameter operation. myWhile(x < 10) { ^

myWhile wird als Funktion hherer Ordnung (siehe Abschnitt 5.2.1 auf Seite 78) de-

niert, da ihr zwei Funktionen als Parameter bergeben werden. Bei der Schreibweise dieser Funktionen kommt eine syntaktische Kurzform zum Einsatz, sie werden durch das Weglassen der sonst notwendigen leeren Klammern zu sogenannten by-name Parametern [WP09]. Statt () => Boolean wird also nur => Boolean geschrieben. Die leeren Klammer knnen danach auch beim Aufruf der Methoden weggelassen werden. by-name Parameter werden, wie ihr Name schon sagt, by-name bergeben, also ohne zuvor ausgewertet zu werden. Im Gegensatz dazu werden normale Parameter einer Funktion by-value bergeben, dh sie werden zuerst ausgewertet, und ihr Wert, also das Ergebnis der Auswertung, danach an die Funktion bergeben. Wie die Fehlermeldung im Beispiel zeigt, wird noch eine weitere Mglichkeit von Scala bentigt, um myWhile zu einem quivalent von while zu machen: Currying, das Thema dieses Abschnitts. myCurriedWhile hat zwei Parameterlisten mit je einem by-name Parameter anstatt eine Parameterliste mit zwei by-name Parametern. Dadurch erhlt man genau den gewnschten Eekt, da der Aufruf von myCurriedWhile eigentlich zwei klassische Funktionsaufrufe bewirkt, wie zu Beginn dieses Abschnitts beschrieben: der erste Funktionsaufruf nimmt den Parameter condition entgegen und gibt einen Funktionswert fr die zweite Funktion zurck. Anstelle eines Ergebniswertes wird also zunchst eine Funktion zurckgegeben. Diese wird dann mit dem zweiten Parameter operation, in diesem Fall ein Block mit mehreren Anweisungen, aufgerufen, und berechnet das Ergebnis.

84

5.2. Ausgangspunkt Mathematik


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

scala> x res1: Int = 0 scala> def myCurriedWhile(condition: => Boolean)(operation: => Unit) { | operation | if(condition) {myCurriedWhile(condition)(operation)}} myCurriedWhile: (condition: => Boolean)(operation: => Unit)Unit scala> myCurriedWhile(x < 10) { | println(x) | x = x + 1 | } 0 1 [...] 8 9

Eine andere Anwendungsmglichkeit des Currying beschreibt Spiewak :

It turns out that the best rationale for using currying has to do with general and specialized functions. It would be nice to dene a function for the general case, but allow users to specialize the function and then use[...] the specialized version on dierent data sets. (aus [Spi08a])

Ein Ausschnitt aus dem zu Beginn dieses Abschnitts angegebenen Beispiel zeigt, wie eine Basisfunktion mittels Currying spezialisiert werden kann:
1 2 3 4 5 6 7 8

scala> def curriedProduct(x: Int)(y: Int) = x * y curriedProduct: (x: Int)(y: Int)Int scala> val twoTimes = curriedProduct(2)_ twoTimes: (Int) => Int = <function1> scala> twoTimes(3) res4: Int = 6

Ein Vergleich dieses Ausschnitts mit der Beschreibung partieller Funktionen in Abschnitt 6.1.1 auf Seite 95, insbesondere mit Listing 6.1 auf Seite 96, macht deutlich wie hnlich beide Konzepte sind. Wampler and Payne bemerken dazu folgendes:

As you can see, currying and partially applied functions are closely related concepts. You may see them referred to almost interchangeably, but whats important is their application [...]. (aus [WP09])

85

5. Konzepte funktionaler Programmierung

5.3. Funktionale Programme


5.3.1. Verzgerte Evaluierung
Bei der verzgerten Auswertung (lazy evaluation) wird das Argument einer Funktion bei seiner ersten Benutzung ausgewertet und nicht schon beim Aufruf der Funktion [Wat96]. Das hat folgende wichtige Konsequenzen: Wird ein Argument nie gebraucht, wird es auch nie ausgewertet, wodurch mglicherweise Rechenzeit gespart wird. Es knnen unausgewertete Funktionen als Argumente an Funktionen bergeben werden. Die Mglichkeiten verzgerter Auswertung werden noch erweitert, wenn auch unausgewertete Ausdrcke als Komponenten in zusammengesetzten Werten zugelassen sind. Diese Ausdrcke werden erst dann ausgewertet, wenn sie aus den zusammengesetzten Werten herausgeholt werden. Dadurch knnen Funktionen nicht nur unausgewertete Argumente erhalten, sondern auch unvollstndig ausgewertete Ergebnisse zurckgeben. Dazu ein Zitat von Watt : Dies hat die bemerkenswerte Konsequenz, das wir unendliche Werte bilden knnen. Insbesondere knnen wir unendliche Listen bilden, deren Schwnze unausgewertet bleiben. Sie werden verzgerte Listen (lazy lists) genannt. (aus [Wat96]) Es handelt sich dabei natrlich nur um eine potentielle Unendlichkeit. Eine verzgerte Liste kann man sich vielmehr wie ein aktives Objekt vorstellen, das immer dann eine neue Komponente berechnen kann, wenn diese bentigt wird. Verzgerte Auswertung untersttzt eine neue Art von Modularitt: die Trennung von Kontrolle und Berechnung. Eine rekursive, sich wiederholende Berechnung wird in einen reinen Berechnungsteil und einen Kontrollteil aufgeteilt. Ersterer dient dazu die ntigen Werte zu berechnen und in eine zusammengesetzte Datenstruktur einzubauen, zB eine verzgerte Liste. Letzterer kann dann diese Datenstruktur ganz oder teilweise durchlaufen und so den Kontrollu des Programmes bestimmen. Beide Teile, Berechnung und Kontrolle, werden dadurch leichter wiederverwendbar. Zudem kann die Berechnung oft einfacher formuliert werden, wenn der Kontrollu auen vor gelassen wird. Scala besitzt einen lazy Modikator. In der Scala-Sprachreferenz beschreibt ihn Odersky folgendermaen: The lazy modier applies to value denitions. A lazy value is initialized the rst time it is accessed (which might never happen at all). (aus [Ode09b]) Zwei wichtige Anwendungsbereiche von "lazy values in Scala sind [Ode09a]:

86

5.3. Funktionale Programme Verhindern aufwendiger Initialisierung von Feldern eines Objektes, bevor diese gebraucht werden. Dadurch wird (wie im untenstehenden Beispiel) das vorzeitige und vielleicht sogar unntige Laden ganzer Datenbank-Tabellen in den Arbeitsspeicher vermieden:
1

lazy val person: Employee = Database.get(personId)

dem Compiler die Initialisierungsreihenfolge berlassen, wenn (wie im folgenden Beispiel aus [Ode09a]) Module miteinander verknpft werden sollen.
1 2 3 4

class Compiler { lazy val symtab = new Symbols(this) lazy val types = new Types(this) }

Bei verzgerter Auswertung hat der Programmierer wenig Kontrolle ber die Auswertungsreihenfolge. Sie pat daher ideal zur rein funktionalen Programmierung ohne Seiteneekte, bei der die Reihenfolge der Auswertung keinen Einu auf das Ergebnis des Programmes hat. Sie pat dagegen nicht gut zu imperativen Sprachen mit ihren Seiteneffekten, da die unberechenbare Auswertungsreihenfolge berraschende und unerwnschte Auswirkungen auf ein Programm haben kann. In Scala sollte der lazy Modikator also nur dort zum Einsatz kommen, wo keine Seiteneekte und kein vernderbarer Zustand involviert ist.

5.3.2. Programme als Funktionen


Ein Programm kann, wenn man von den Details der Berechnungen Abstand nimmt und sich anstatt auf das wie auf das was, also das Ergebnis der Berechnungen, konzentriert, als eine blackbox angesehen werden, die Eingaben (Input) in Ausgaben (Output) verwandelt [Lou03]. Aus dieser Perspektive entspricht ein Programm also einer mathematischen Funktion. Insofern hneln sich in der funktionalen Programmierung Programme und Funktionen, nur Input und Output sehen jeweils etwas anders aus. Bei imperativen Programmen funktioniert diese vereinheitlichte Sichtweise nicht. Hchstens einzelne achtsam programmierte Prozeduren knnten als blackbox ohne Seiteneekte angesehen werden, ganze Programme auf keinen Fall.

5.3.3. Modellieren von Zustnden


Als einer der Erfolgsfaktoren der imperativen Programmierung wurde die natrliche Modellierung der Wirklichkeit mit zustandsbehafteten Variablen beschrieben (siehe Abschnitt 2.2 auf Seite 5). In der reinen funktionalen Programmierung gibt es aber keine zustandsbehafteten Variablen - wie sollen die dynamischen Zustnde natrlicher Objekte der realen Welt dann durch funktionale Programme beschrieben werden? Dieses Ziel ist in der rein funktionalen Programmierung nur indirekt zu erreichen, indem Zustnde modelliert werden, ohne Speicher zu benutzen [Wat96]. Der Zustand eines Objektes kann

87

5. Konzepte funktionaler Programmierung durch eine Funktion modelliert werden, die den gegenwrtigen Wert des Objektes als Argument hat. Ein Proze, also der sich im Zeitablauf ndernde Zustand eines Objektes, kann ber Kanle modelliert werden. Ein Kanal kann dabei als eine Liste aufeinander folgender Nachrichten verstanden werden. Der sich stndig verndernde Zustand beispielsweise eines Bankkontos wird dann als Funktion von einem Eingabekanal zu einem Ausgabekanal dargestellt. Dabei mu es natrlich mglich sein Kanle einzurichten und als Argument an einen Proze zu bergeben, bevor irgendeine der Nachrichten ausgewertet wurde. Ein Kanal wird daher als verzgerte Liste dargestellt (siehe Abschnitt 5.3.1 auf Seite 86), deren Komponenten erst ausgewertet werden, wenn auf sie zugegrien wird. Bei jeder rekursiven Aktivierung wartet die das Bankkonto modellierende Funktion auf eine Nachricht aus dem Anfragenkanal, berschreibt ihren Kontostand und gibt eine Antwort auf den Antwort-Kanal. Das Bankkonto kann dann mit jedem Teil des Programms kommunizieren, an den seine Kanle bergeben werden. Diese Art der Modellierung von Zustnden ist alles andere als intuitiv und die Organisation des Datenusses kann viel Programmierarbeit erfordern. Zudem knnen Prozesse zwar funktional modelliert werden, aber es kann nicht ausgedrckt werden wie sie verbunden sind. Bei einem Konto spielt die Frage, ob eine Abhebung vor oder nach einer Einzahlung erfolgt, eine entscheidende Rolle fr den Erfolg der Transaktion. Wenn nun zwei unabhngige Benutzerprozesse auf dasselbe Konto zugreifen, beeinut die Reihenfolge der Anfragen das Verhalten, diese ist aber nicht vorhersehbar. Funktionale Programme sollten aber deterministisch sein - der Aufruf einer Funktion mit bestimmten Parametern sollte immer dasselbe Ergebnis liefern, unabhngig von anderen Prozessen oder der Reihenfolge der Anfragen. Da Zustnde in Software-Anwendungen fr die reale Welt eine wichtige Rolle spielen, haben viele funktionale Programmiersprachen imperative Konzepte wie Zuweisungen und zustandsbehaftete Variablen bernommen. Dies ist natrlich alles andere als ideal, da viele Vorteile der reinen funktionalen Programmierung durch diese konzeptionelle Verschmutzung verloren gehen. Moseley and Marks bemerken zu diesem Thema, da die trotz der vielen Argumente zugunsten der funktionalen Programmierung bisher fehlende breite Akzeptanz dieses Paradigmas in der Praxis der Softwareentwicklung auch ihre Berechtigung haben knnte:

[...] the fact remains that such arguments have been insucient to result in widespread adoption of functional programming. We must therefore conclude that the main weakness of functional programming is the ip side of its main strength namely that problems arise when (as is often the case) the system to be built must maintain state of some kind. (aus [MM06])

Es gibt allerdings auch schon Lsunganstze fr das Problem der Modellierung von Zustand in der funktionalen Welt. In Abschnitt 5.4.3 auf Seite 91 soll einer davon, die sogenannten Monaden, vorgestellt werden.

88

5.4. Funktionale Datentypen

5.4. Funktionale Datentypen


5.4.1. ADT und Mustervergleich
In Abschnitt 3.3 auf Seite 20 wurden abstrakte Datentypen als berwiegend mit der funktionalen Programmierung assoziierte Form der benutzerdenierten Datenabstraktion vorgestellt. Abbildung 5.1 zeigt noch einmal, wie eine Datenabstraktion Liste anhand der Beobachtungen (den Zeilen der Matrix) zerlegt werden kann.

constructor of s observations
null?(s) head(s) tail(s) nil true error error adjoin(s , n) false n s

Abbildung 5.1.: Eine Datenabstraktion Liste nach Beobachtungen zerlegt (nach [Coo90]) Die Zerlegung anhand der Beobachtungen entspricht der ADT-Sicht auf die Datenabstraktion [Coo90]. Jede Zeile der Datenmatrix wird zu einer unabhngigen Operation des ADT, die den in der Matrix angegebenen Wert zurckgibt, wenn sie auf einen bestimmten Konstruktor-Wert angewendet wird (die Spalten der Matrix werden als Konstruktoren bezeichnet). Abbildung 5.2 auf der nchsten Seite zeigt die Implementierung der Datenabstraktion als ADT. Die Operationen null?, head und tail geben unterschiedliche Werte zurck, je nachdem, ob sie auf NIL oder CELL angewandt werden. Diese beiden Reprsentationswerte werden durch die Konstruktoren nil und adjoin erzeugt, die ebenfalls als Operationen innerhalb des ADT implementiert sind. Es ist mglich, Variablen vom Typ des in Abbildung 5.2 auf der nchsten Seite dargestellten ADT "Integer-Liste zu deklarieren und die dort denierten Methoden zu verwenden, um Listen zu erzeugen und zu manipulieren, wie das nachfolgende Beispiel aus [Coo90] demonstriert:
1 2

var l : list = adjoin(adjoin(nil, 4), 7) if equal(nil, l) then ...

Die Reprsentation der Listen bleibt allerdings verborgen. Es ist fr einen Client, der die Liste l besitzt, nicht mglich, auf NIL oder CELL zuzugreifen. Die Operationen null?, head und tail in Abbildung 5.2 auf der nchsten Seite sind Beispiele fr das in funktionalen Sprachen weit verbreitete Konzept des Mustervergleichs (pattern matching). Im allgemeinen kann eine Funktion mit mehreren Gleichungen deniert werden, von denen jede eine verschiedene linke Seite hat. Jede linke Seite enthlt ein Muster in seiner formalen Parameterposition. Das Muster mu zum Ar-

89

5. Konzepte funktionaler Programmierung adt representation operations IntList list = NIL | CELL of integer list nil = NIL adjoin(x: integer, l: list) = CELL(x,l) null?(l: list) = case l of NIL true CELL(x,l) f alse NIL error CELL(x,l) x NIL error CELL(x,l) l

head(l: list) = case l of

tail(l: list) = case l of

Abbildung 5.2.: ADT-Implementierung fr eine Datenabstraktion Liste (nach [Coo90]) gument der Funktion passen, damit die Gleichung angewendet wird. (aus [Wat96]) Die Operation null?(l : list) kann also als Funktionsdenition mit zwei Gleichungen verstanden werden, deren linke Seiten NIL und CELL sind. Wird der Operation eine durch die Operation nil erzeugte leere Liste l bergeben, deren Reprsentation NIL ist, wird die erste Gleichung angewendet und true zurckgegeben. Wird eine durch die Operation adjoin erzeugte nicht-leere Liste bergeben, deren Reprsentation CELL ist, wird die zweite Gleichung angewendet und false zurckgegeben. Die beiden Gleichungen knnen als Flle der Funktion aufgefat werden, was durch das Schlsselwort case ausgedrckt wird. Diese knappe und klare Art der Funktionsdenition erinnert an die vertraute mathematische Schreibweise (siehe Gleichung 5.1). null?(l) = true fr l = NIL f alse fr l = CELL (5.1)

Auch hier wird das Argument mit Mustern verglichen und bei bereinstimmung der entsprechende Wert zurckgegeben.

5.4.2. Unvernderliche Daten und Kollektionen


Im Groen und Ganzen haben funktionale Sprachen hnliche Datentypen wie andere Sprachen auch [Wat96] (siehe Abschnitt 2.2.4 auf Seite 8). In funktionalen Sprachen werden aber unvernderliche (immutable ) Datentypen und Kollektionen den vernderlichen (mutable ) Typen vorgezogen, da diese die funktionale Philosophie des Vermeidens von Seiteneekten untersttzen. Funktionale Programmierer mssen daher eine Kopie eines

90

5.4. Funktionale Datentypen unvernderlichen Wertes herstellen, die sich in einer oder mehreren Komponenten unterscheidet, wo imperative Programmierer einfach die zu ndernden Komponenten eines vernderlichen Wertes berschreiben [Wat96]. Die scheinbare unertrgliche Inezienz des stndigen Kopierens ganzer Kollektionen wird in funktionalen Sprachen durch trickreiche Implementierungen abgemildert. Scala bietet mit seinen vals die Mglichkeit unvernderliche Variablen zu denieren (im Gegensatz zu den vernderlichen vars). Unvernderlich bedeutet in dem Zusammenhang, der Variablen kann keine neue Referenz zugewiesen werden. Zudem ist die Bibliothek der Kollektionen in Scala zweigeteilt: von den meisten Kollektionen gibt es sowohl eine vernderliche als auch eine unvernderliche Version [OSV08]. Es wird empfohlen, beim Programmieren zunchst mit unvernderlichen Kollektionen zu beginnen, da diese einfacher zu handhaben und kompakter zu speichern sind als die vernderlichen Kollektionen. Scala bietet dem Programmierer einiges an syntaktischem Zuckergu um einen eventuell notwendigen spteren Wechsel von unvernderlichen zu vernderlichen Kollektionen zu erleichtern.

5.4.3. Monaden als Tor zur realen Welt


Pepper schreibt im letzten Teil von [Pep03] folgendes ber die funktionale Programmierung: Bis jetzt haben wir ein wunderschnes Gebude errichtet - wohlfundiert, weitlug und hoch aufragend. Es fehlt nur noch eine Kleinigkeit: die Tren, durch die man reingehen kann, und die Fenster, durch die man reingucken kann. Prosaischer ausgedrckt: Wir mssen die Abstraktion unserer funktionalen Programme mit der konkreten Umwelt realer Computer verbinden, in denen es Dateien, Bildschirme und Tastaturen gibt, die von einem alles beherrschenden Betriebssystem gesteuert werden, und wo sich alles mit dem Flu der Zeit ndert. (aus [Pep03]) Das pure, am mathematischen Funktionsbegri orientierte funktionale Programmierparadigma stt da an seine Grenzen, wo ein Programm nicht mehr als blackbox angesehen werden kann, die bei einer bestimmten Eingabe deterministisch und vorhersehbar stets die gleiche Ausgabe liefert. Das ist bei der dialogartigen Interaktion zwischen Benutzer und Computer der Fall, also im Bereich des Input/Output (I/O) ber die Computerhardware. Dazu noch einmal ein Zitat von Pepper : Funktionale Programmierung nimmt eine ganz starke Abstraktion vor: Man ignoriert (fast) vllig, wie Ergebnisse auf einem Computer zustande kommen, und konzentriert sich ausschlielich darauf, welche Ergebnisse man haben will. In dieser Abstraktion steckt ein Gutteil der Eleganz und Programmiersicherheit der Methode. In jeder Abstraktion steckt aber die Gefahr, da man sie zu weit treibt. [...]

91

5. Konzepte funktionaler Programmierung Wir drfen zwar von der Arbeitsweise des Rechners abstrahieren, aber nicht von der Arbeitsweise des Benutzers. Und aus dem Blickwinkel des Benutzers - oder allgemeiner: der Umgebung, in der das Programm abluft - gibt es zwei Aspekte, die wir nicht ignorieren drfen: den Ablauf der Zeit [und] die Identitt der Objekte. (aus [Pep03]) Aus diesen Einsichten folgert Pepper in Bezug auf die dialogartige Kommunikation zwischen Mensch und Maschine, also fr die Programmierung im Bereich I/O: Jede Kommunikation mit dem Benutzer (allgemeiner: mit der Auenwelt) mu die zeitliche Abfolge von Aktivitten explizit ausdrcken knnen. [...] Gegenstnde der Auenwelt sind keine Werte. Jeder Bezug auf solche Objekte mu sicherstellen, da ihre Identitt gewahrt bleibt; sie knnen weder dupliziert noch eliminiert werden. (aus [Pep03]) Um das Programmieren fr die Anforderungen der realen Welt zu erleichtern und die Ezient der Ausfhrung zu verbessern, haben viele funktionale Sprachen imperative Eigenschaften angenommen [Seb96]. Auch der Urahn aller funktionalen Sprachen, Lisp, ist relativ schnell diesen Weg gegangen, der aber viele Vorteile des funktionalen Paradigmas konterkariert. Es ist daher verstndlich, da Monaden als mglicher Ausweg aus diesem Dilemma groe Beachtung in der funktionalen Welt gefunden haben. Der geheimnisvolle Name stammt aus dem Griechischen1 und spielte auch in der Philosophie, insbesondere bei Leibnitz2 , eine Rolle. Die ursprngliche, philosophische, Bedeutung des Monadenbegris hat aber bei den praktischen Anwendungen in der Informatik keine groe Bedeutung mehr [Pep03]. Die zugrundeliegenden programmiersprachlichen Ideen sind schon lter als die Verwendung des Begries Monaden in der Informatik. Wenn man die Continuations der 70er Jahre mit der Idee der Verschattung (information hiding) verknpft, hat man die Monaden von heute. Continuations sind eine spezielle Form von Funktionen hherer Ordnung, bei der man einer Funktion ihre Fortsetzung als zustzliches Argument mitgibt. Aus g(f(x)) wird also f(x,g).
1 2

abgeleitet von monos, dt. einzeln, allein [BBD+ 03] Folgendes Zitat von Toll erlutert die Bedeutung des Monadenbegries bei Leibnitz: Monaden oder woraus die Wirklichkeit besteht Kriterium aller Substanz war fr Leibniz ihre Wirkung als Kraft. Gleich einem Punkt als abstrakter, kleinster geometrischer Einheit, ist eine Monade der kleinste Kraftpunkt. Leibniz bezeichnete sie als die wahrhaften Atome der Natur. Die Monaden waren fr Leibniz also unteilbar und gestaltlos. Genauso wenig kann sich eine Monade, gleich einem Funktionsdierential, ausdehnen. Ihre Intensitt kann nur mebar beziehungsweise numerisch beschreibbar sein. Betrachten wir Leibniz Monaden aus unserer heutigen Wissensperspektive, so sind Analogien zu Elementarteilchen oder dem Plankschen Wirkungsquantum nicht von der Hand zu weisen. Leibniz beschrieb diese Monaden als je einzigartig, die weder vernichtet noch erzeugt werden knnen, auch knne nichts in die Monaden hinaus- oder hineinwirken. (aus [Tol09])

92

5.4. Funktionale Datentypen Monaden knnen folgendermaen charakterisiert werden [Stu07]: Konzept zur Strukturierung funktionaler Programme. Integration spezieller Eekte der imperativen Programmierung in die funktionale Programmierung ohne Verlust der referentiellen Integritt (die wichtige Eigenschaft von Funktionen, da ihr Wert ausschlielich von den Werten ihrer Argumente und nicht-lokaler Konstanten abhngt [Lou03]). Ihr Einsatzgebiet liegt in der Fehlerbehandlung, der Schaung globaler Variablen sowie der Ein- und Ausgabe. Sie knnen folgendermaen deniert werden: Eine Monade M a ist ein parametrisierter Datentyp, der eine Berechnung, die einen Wert vom Typ a liefert, kapselt. [...] Eine Monade ist ein Tripel (M, unit, *), bestehend aus einem Typkonstruktor M und zwei Funktionen polymorphen Typs (aus [Stu07]). Monaden entsprechen also einem abstraktem Datentyp in der funktionalen Programmierung. Aus Sicht der Objekt-orientierten Programmierung kann man die Typdeklaration einer Monade folgendermaen interpretieren [PJW93]: Die Typkonstruktion M entspricht der Deklaration des Monadentyps Die Einheitsfunktion unit bernimmt die Rolle der Konstruktormethode. Die Bindefunktion * enthlt die fr die Ausfhrung notwendige Logik. Die folgende Scala-Klasse (aus [Iry07]) zeigt, wie mit den Methoden map und flatMap (sowie der Konstruktormethode unit) eine Monade erzeugt werden kann:
1 2 3 4 5

class M[A](value: A) { private def unit[B] (value : B) = new M(value) def map[B](f: A => B) : M[B] = flatMap {x => unit(f(x))} def flatMap[B](f: A => M[B]) : M[B] = ... }

Alle Monaden sind functors (ein mathematischer Begri, der im Abschnitt 7.3.1 auf Seite 115 nher erlutert wird), weshalb das folgende Zitat von Iry als anschauliche Erklrung der obigen Monaden-Klasse dienen kann: In Scala a functor is a class with a map method and a few simple properties. For a functor of type M[A], the map method takes a function from A to B and returns an M[B]. In other words, map converts an M[A] into an M[B] based on a function argument. Its important to think of map as performing a transformation and not necessarily having anything to do with loops. It might be implemented as a loop, but then again it might not. (aus [Iry07])

93

5. Konzepte funktionaler Programmierung Die Klassendenition class M[A](value: A) entspricht also der Denition eines Monadentyps. Die map Methode, die sich der flatMap Methode bedient, entspricht der Bindefunktion *, wobei die Abbildung f: A => B die Ausfhrungslogik darstellt. Die Methode unit konstruiert mittels new M(value) ein neues Monaden-Objekt, wobei value das Ergebnis der Transformation durch map, also B, darstellt. Durch die Datenkapselung in einer Monade M a knnen zwei Berechnungen gleichzeitig ablaufen, eine uneingeschrnkte Berechnung auf der Ebene der Ergebnisse (vom Typ a) und eine eingeschrnkte Berechnung innerhalb der Monade (durch auf ihr denierte primitive Operationen) [Stu07]. Der Programmierer kann sich ganz auf die uneingeschrnkte Berechnung konzentrieren. Die Wartbarkeit, Modularitt und Lesbarkeit von Programmen wird durch Monaden erhht. Nachtrgliche Anpassungen lassen sich oft auf die Denition einer Monade beschrnken ohne das gesamte Programm umzustrukturieren. Monaden sind allerdings kein Allheilmittel fr die Probleme der funktionalen Programmierung mit der nicht immer funktionalen Realitt: Man mu die Euphorie jedoch dmpfen. Zwar lassen sich gewisse Probleme mit Monaden behandeln. Aber diese Behandlung ist alles andere als elegant. Denn es passiert allzu oft, da man den Beschrnkungen der Monaden die eigentliche Lsungsstruktur des Algorithmus opfern mu, so da die Programme ziemlich komplex und undurchschaubar werden (aus [Pep03]). Obwohl Monaden nicht explizite Bestandteile der Sprache sind, spielen sie in Scala doch eine Rolle. Beispielsweise kann Scalas for Ausdruck als Syntax fr Monaden angesehen werden, wie Abschnitt 6.5 auf Seite 106 zeigt.

94

6. Elemente Funktionaler Programmierung in Scala


6.1. Funktionen und Closures
6.1.1. Funktionen
Teile und herrsche (divide and conquer) ist ein wichtiger Grundsatz in der Informatik. Je grer und komplexer Programme werden, desto notwendiger wird es sie in kleinere berschaubare Teile zu zerlegen. Im Falle des Kontrollusses erfolgt diese Zerlegung indem der Quellcode auf Funktionen verteilt wird. Obwohl dieses Vorgehen auch in Objektorientierten Sprachen wie Java blich ist, bietet die Objekt-funktionale Sprache Scala hier doch deutlich mehr Mglichkeiten [OSV08]: Methoden sind Funktionen, die an ein Objekt gebunden sind. In einer Sprache wie Java gibt es nur Methoden, Funktionen auerhalb von Objekten sind nicht mglich. Javas Methoden haben immer einen Rckgabewert, entsprechen also der klassischen Denition von Funktionen. Prozeduren ohne Rckgabewert werden ber Methoden mit dem Rckgabewert void simuliert. In Scala sehen Methoden ganz hnlich aus wie in Java. Bei Prozeduren wird allerdings Unit statt void zurckgegeben, und im Sinne einer einheitlichen und konsistenten Syntax werden auch Funktionsdenitionen in Scala mit einem Schlsselwort (def) eingeleitet. Lokale Funktionen sind Funktionen, die lokal innerhalb einer anderen Funktion deniert werden und nur innerhalb dieser umgebenden Funktion sichtbar sind. Sie haben vollen Zugri auf die Parameter der umgebenden Funktion. Lokale Funktionen sind oft Hilfsfunktionen, die hnlich wie private Methoden vor der Auenwelt verborgen werden. Der Zugrismodikator private kann in Scala aber nur auf Methoden, also innerhalb von Klassen, angewendet werden. Lokale Funktionen knnen dagegen auch auerhalb von Klassen deniert werden. Funktionen erster Klasse sind (konzeptionell) nicht an ein Objekt bzw eine Klasse gebunden, sondern selbststndige und vollwertige Bestandteile der Sprache. Sie besitzen einen eigenen Typ, man kann sie Variablen zuweisen oder als Argumente bergeben [Gro09]. Funktionsliterale sind unbenannte Funktionen erster Klasse, die als Literale hingeschrieben und dann als Funktionswerte im Programm herum gereicht werden knnen (siehe auch Abschnitt 5.2.4 auf Seite 82 bezglich by-name Parametern, einer syntaktischen Kurzform fr Funktionsliterale als Parameter von Funktionen hherer

95

6. Elemente Funktionaler Programmierung in Scala Ordnung ). Die Literale existieren im Programmtext. Sie werden als Instanz einer der vorgegebenen FunctionN-Traits in Scala (N steht fr die Anzahl der Argumente, zB Function0, Function1, etc) kompiliert und existieren zur Laufzeit als Objekte. Funktionsliterale (function literals) und Funktionswerte (function values) stehen also in einem hnlichen Verhltnis zueinander wie Klassen (existieren im Programmtext) und Objekte (existieren zur Laufzeit). Partielle Funktionen sind Ausdrcke, in denen einer Funktion mit Parametern nicht alle bentigten (oder gar keine) Argumente bergeben werden. Die resultierenden (partiellen) Funktionsausdrcke knnen in Variablen gespeichert werden. Nach dem Kompilieren verweisen die Variablen dann auf Funktionswerte, die ber den Variablennamen mit den fehlenden Parametern aufgerufen werden knnen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

object ScalaFunctions extends Application { // Funktion erster Klasse def squaredSum(x: Int, y: Int) { // Lokale Helferfunktionen def sum(x: Int, y: Int) = x + y def square(sum: Int) = sum * sum println(square(sum(x,y))) } squaredSum(2,5) val partialSquaredSum = squaredSum(2, _:Int) // Partielle Funktion partialSquaredSum(5) val partialSquaredSum2 = squaredSum _ // Partielle Funktion partialSquaredSum2(2,5) }

Listing 6.1: Funktionen in Scala. Listing 6.1 gibt dreimal den Wert 49 aus, einmal als Ergebnis des direkten Aufrufs der Funktion squaredSum mit ihren beiden Helferfunktionen sum und square, dann als Ergebnis der beiden partiellen Funktionen. Bei partialSquaredSum wurde die Funktion squaredSum nur auf ihr erstes Argument angewandt, das zweite Argument mu also beim Aufruf der partiellen Funktion mitgegeben werden. Bei partialSquaredSum2 wurde die Funktion squaredSum auf keines ihrer Argumente angewandt, beide Argumente mssen beim Aufruf der partiellen Funktion mitgegeben werden (man beachte die sparsame Syntax mit dem einzelnen Platzhalter-Unterstrich). Das sieht dann wieder fast genauso aus wie beim Aufruf der Originalfunktion, dennoch handelt es sich auch hier um eine partielle Funktion.

6.1.2. Closures
Closures (Funktionsabschlsse) sind ein eng mit den Funktionen erster Klasse verwandtes Konzept [Gro09]. So eng, da beide Begrie oft als Synonyme gebraucht und in vielen

96

6.1. Funktionen und Closures Sprachen zu einem einzigen Konstrukt zusammengefat werden - so auch in Scala. Die Bedeutung von Closures lt sich gut an Funktionsliteralen mit und ohne freien Variablen veranschaulichen [OSV08]: Geschlossene Terme (closed terms) sind Programmstcke ohne freie Variablen. Ein Funktionsliteral wie
1

(x: Int) => x + 1

ist ein geschlossener Term weil x eine gebundene Variable ist, also eine Variable die (in diesem Fall als Funktionsparameter) eine Bedeutung im Kontext der Funktion hat, und es keine freien Variablen gibt. Oene Terme (open terms) sind Programmstcke mit freie Variablen. Ein Funktionsliteral wie
1

(x: Int) => x + y

ist ein oener Term weil y eine freie Variable ist, also eine Variable, die keinerlei Bedeutung im Kontext der Funktion hat. Whrend der Wert von x durch die bergabe einer ganzen Zahl als Funktionsparameter festgelegt wird, mu der Typ und der Wert von y irgendwo auerhalb des Funktionsliterals bestimmt werden. Der Name Closure (Einschlu) rhrt daher, da die freie Variable (nicht das Objekt, auf das sie verweist) vom Funktionsliteral eingeschlossen wird [VS10]. Sptere nderungen der Variablen werden innerhalb der Funktion wirksam, genauso wie nderungen der Variablen innerhalb der Funktion nach auen hin wirksam werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14

scala> var y = 2 y: Int = 2 scala> val openTerm = (x: Int) => x + y openTerm: (Int) => Int = <function1> scala> openTerm(2) res0: Int = 4 scala> y = 1 y: Int = 1 scala> openTerm(2) res1: Int = 3

Die im obigen Beispiel auerhalb der Funktion erster Klasse (bzw des Funktionsliterals bzw des Funktionseinschlusses) openTerm denierte Integer-Variable y wird als freie Variable in die Funktion eingebunden und mit dem aktuellen Wert y=2 belegt. Wird dieser Wert nachtrglich gendert(y=1), macht sich das in der Funktion bemerkbar. Ein erneuter Aufruf der Funktion mit dem selben Parameterwert 2 ergibt jetzt 2+1=3 anstatt 2+2=4.

97

6. Elemente Funktionaler Programmierung in Scala


1 2 3 4 5 6 7 8 9 10

scala> var z = 2 z: Int = 2 scala> val changeFreeVariable = () => z = z + 1 changeFreeVariable: () => Unit = <function0> scala> changeFreeVariable() scala> z res9: Int = 3

Die Funktion changeFreeVariable ist eine Prozedur, da sie Unit als Rckgabewert hat. Sie hat Seiteneekte, da sie den Wert der auerhalb denierten freien Integer-Variablen z um eins erhht. Sie ist also kein gutes Beispiel fr den funktionalen Programmierstil, aber sie demonstriert, da die nderung der freien Variablen innerhalb eines Closure nach auen wirksam wird.

6.2. Listen und Tupel


6.2.1. Listen
Listen spielen in der funktionalen Programmierung eine herausragende Rolle. Der Urahn aller funktionalen Sprachen, Lisp, verdankt seinen Namen seiner ursprnglichen Bestimmung: Listenverarbeitung (list processing). Zudem hat (reines) Lisp nur zwei Datenstrukturen, Atome und Listen. Daten und Programme werden in Lisp einheitlich als Listen dargestellt. Wenn das erste Element einer Liste eine Funktion ist, handelt es sich um ein Programm, ansonsten um eine Datenstruktur [Seb96]. In Scala sind Listen nur eine Datenstruktur, aber vermutlich die wichtigste und am hugsten benutzte [OSV08]. Sie hneln Arrays, sind aber unvernderlich (immutable), dh ihre Elemente knnen nicht durch Zuweisung verndert werden. Zudem haben sie eine rekursive Struktur (siehe Abschnitt 2.2.4 auf Seite 8), whrend Arrays "ach sind. Wie Arrays sind auch Listen in Scala homogen, dh alle Elemente einer Liste sind vom selben Typ (oder von abgeleiteten Subtypen). Der Typ einer Liste, List[T], ist kovariant (siehe Abschnitt 7.3.4 auf Seite 118). Wenn also S ein Subtyp von T ist, dann ist auch List[S] ein Subtyp von List[T], wie im nachfolgenden Beispiel demonstriert:
1 2 3 4 5 6 7 8 9 10 11

scala> val d: List[AnyRef] = List(() => 1 + 1, () => 2 + 2) d: List[AnyRef] = List(<function0>, <function0>) scala> val c: List[String] = List("hello", "world") c: List[String] = List(hello, world) scala> def meth(l : List[AnyRef]) = | | println(l) meth: (l: List[AnyRef])Unit scala> meth(d)

98

6.2. Listen und Tupel


12 13 14 15

List(<function0>, <function0>) scala> meth(c) List(hello, world)

Die Kovarianz von Listen wird in Scala durch die Unvernderlichkeit der Datenstruktur Liste ermglicht. Die vernderlichen Listen von Java sind dagegen invariant (siehe Abschnitt 7.3.6 auf Seite 121). Die leere Liste hat in Scala den Typ List[Nothing] und ist dadurch Subtyp aller anderen Listen. Listen knnen erzeugt werden, indem sie als Literale hingeschrieben oder mit dem cons -Operator :: zusammengebaut werden. Listing 6.2 zeigt beispielhaft den Umgang mit Listen in Scala. Es gibt noch viel mehr Methoden als hier aufgefhrt [OSV08].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

object ScalaLists extends Application { // Erzeugung von Listen val pal= List(o, t, t, o) // "pal" steht fuer "palindrom" val num = 1 :: 2 :: 3 :: 4 :: Nil // "list-construction" // Indizierung von Listen println("""// Indizierung von Listen""") println("pal.indices: " + pal.indices) // Grundlegende Listen- Operationen println("\n" + """// Grundlegende Listen- Operationen""") println("num.head: " + num.head) println("num.tail: " + num.tail) println("num.tail.head: " + num.tail.head) println("num.isEmpty: " + num.isEmpty) // Mustervergleich (pattern matching) bei Listen println("\n" + """// Mustervergleich (pattern matching) bei Listen""") val List(a,b,c,d) = pal println("val List(a,b,c,d) = pal") println("a: " + a) println("c: " + c) val h :: rest = num println("val h :: rest = num") println("h: " + h) println("rest.head: " + rest.head) println("rest.tail: " + rest.tail)

// Methoden erster Klasse fuer Listen println("\n" + """// Methoden erster Klasse fuer Listen""") println("pal ::: num: " + (pal ::: num)) // "list-concatenation" println("num.length: " + num.length) println("num.last: " + num.last) println("num.init: " + num.init) println("pal.reverse: " + pal.reverse)

99

6. Elemente Funktionaler Programmierung in Scala


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

println("pal.take(2): " + pal.take(2)) println("pal.drop(2): " + pal.drop(2)) println("num.splitAt(2): " + num.splitAt(2)) println("num zip pal: ") println(num zip pal) println("pal.mkString: " + pal.mkString) // Methoden hoeherer Ordnung fuer Listen println("\n" + """// Methoden hoeherer Ordnung fuer Listen""") val words = List("ot", "to") println("""val words = List("ot", "to")""") println("(words flatMap (_.toList)): " + (words flatMap (_.toList))) println("(pal filter (_.equals(o))): " + (pal filter (_.equals(o)))) println("(pal partition (_.equals(o))): " + (pal partition (_.equals(o))) ) }

52

Listing 6.2: Listen in Scala Die Ausgabe von Listing 6.2 auf der vorherigen Seite zeigt, da die Indizierung von Listen wie bei Arrays mit 0 beginnt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

// Indizierung von Listen pal.indices: Range(0, 1, 2, 3) // Grundlegende Listen- Operationen num.head: 1 num.tail: List(2, 3, 4) num.tail.head: 2 num.isEmpty: false // Mustervergleich (pattern matching) bei Listen val List(a,b,c,d) = pal a: o c: t val h :: rest = num h: 1 rest.head: 2 rest.tail: List(3, 4) // Methoden erster Klasse fuer Listen pal ::: num: List(o, t, t, o, 1, 2, 3, 4) num.length: 4 num.last: 4 num.init: List(1, 2, 3) pal.reverse: List(o, t, t, o) pal.take(2): List(o, t) pal.drop(2): List(t, o) num.splitAt(2): (List(1, 2),List(3, 4))

100

6.2. Listen und Tupel


28 29 30 31 32 33 34 35 36

num zip pal: List((1,o), (2,t), (3,t), (4,o)) pal.mkString: otto // Methoden hoeherer Ordnung fuer Listen val words = List("ot", "to") (words flatMap (_.toList)): List(o, t, t, o) (pal filter (_.equals(o))): List(o, o) (pal partition (_.equals(o))): (List(o, o),List(t, t))

Mit den drei grundlegenden Listenoperationen head, tail und isEmpty knnen alle anderen Operationen auf Listen ausgedrckt werden. Die Vielzahl der vordenierten Listenoperationen in Scala dient also vor allem der Bequemlichkeit und Produktivitt der Programmierer. Listen knnen anstatt mit Methoden auch mittels Mustervergleich auseinandergenommen werden. Kennt man die genaue Anzahl der Listenelemente nicht, kann man sich des cons-Operators :: bedienen:
1 2

a :: rest // ein Muster, das auf Listen der Laenge >= 1 zutrifft a :: b :: rest // ein Muster, das auf Listen der Laenge >= 2 zutrifft

Trit das zweite Muster auf eine bergebene Liste zu (weil sie beispielsweise 5 Elemente hat) wird das erste Listenelement a zugewiesen, das zweite b und die anderen drei als Liste dem Bezeichner rest. Bei den Listenmethoden unterscheidet man: Methoden erster Klasse, die keine Funktionen als Argumente nehmen. Methoden hherer Ordnung mit Funktionen als Argumenten Methoden auf dem scala.List Objekt, wie zB List.range(1,4), die List(1,2,3,4) erzeugt. Listen sind die wahren Arbeitspferde der Scala-Programmierung. Es lohnt sich sie gut zu kennen [OSV08].

6.2.2. Tupel
Tupel hneln Listen und Arrays, knnen aber Elemente unterschiedlichen Typs enthalten [OSV08]:
1 2 3 4 5

scala> val dataRow = ("Mueller", "Hans", 180, 82, Array("Haus", "Auto", "Familie")) | dataRow: (java.lang.String, java.lang.String, Int, Int, Array[java.lang.String]) = (Mueller,Hans,180,82,[Ljava.lang.String;@1d4ee7e)

Das Tupel dataRow enthlt Strings, Integers und ein Array[String]. Es hnelt (abgesehen von dem Array) einer Zeile in einer betrieblichen Excel-Tabelle, wie sie vermutlich millionenfach auf der Welt existieren. Fr die Auswertung solcher Daten verwenden Statistiker

101

6. Elemente Funktionaler Programmierung in Scala bevorzugt spezielle Statistik-Software, wie die auf dem Lisp-Dialekt Scheme basierende Open-Source Software R. Obwohl R auch eine gewisse Untersttzung fr die Objektorientierte Programmierung bietet, spielen dort Tupel (und die hnlichen data.frames ) eine groe Rolle bei der Arbeit mit Daten [Lig08]. In Scala sind Tupel hingegen nur fr sehr einfache Kombinationen von Daten zu empfehlen. In allen anderen Fllen sollte eine Klasse deniert werden, schlielich besitzt Scala mchtige Objekt-orientierte Konzepte, ganz anders als beispielsweise R. Durch das Denieren einer Klasse macht der Programmierer seine Intentionen gegenber Lesern des Programmtextes explizit und gibt dem Compiler und der Sprache die Mglichkeit, Fehler zu erkennen [OSV08]. Das oben denierte Tupel dataRow wre sicher ein Kandidat fr eine Klasse Mitarbeiter. Wenn 180 und 82 dann Werte der Instanzvariablen groesse und gewicht dieser Klasse wren, knnte man ihre Bedeutung viel leichter erkennen. Zudem reprsentiert eine Klasse in Scala (wie in den meisten anderen Objekt-orientierten Sprachen) einen Typ. In einer statisch typisierten Sprache wie Scala kann der Compiler sowohl den Typ einer Klasse als den Typ ihrer Felder berprfen, wodurch die Benutzung eines Mitarbeiter Objektes in einem Programm sicherer wird als die Benutzung eines Tupels mit der gleichen Information. Tupel haben jedoch auch in Scala eine verbreitete Anwendung: sie eignen sich sehr gut dazu mehrere Werte (unter Umstnden verschiedenen Typs) von einer Methode zurckzugeben:
1 2 3 4 5

scala> def twoTimes(x: Int, name: String) = (x, x, name, name) twoTimes: (x: Int,name: String)(Int, Int, String, String) scala> twoTimes(2, "times") res0: (Int, Int, String, String) = (2,2,times,times)

6.3. Case Classes und Pattern Matching


In der Objekt-orientierten Programmierung ist die Klassenhierarchie das zentrale Konstrukt zur Darstellung strukturierter Daten. Zur Inspektion dieser Daten mssen die entlichen Methoden der Klassen verwendet werden. Diese sollten also mglichst vollstndig alle sinnvollen Operationen auf den Daten widerspiegeln. Es kann beim klassenbasierten Ansatz arbeitsaufwendig und fehleranfllig sein, nachtrglich neue Methoden einzufhren, denn dafr mssen unter Umstnden viele Klassen der Hierarchie gendert werden, oder es mte von ihnen abgeleitet werden. Zudem ist es oft schwierig, die Operationen zu durchschauen und zu ndern, da sie ber viele Klassen verteilt sind [OAC+ 04]. Wie in Abschnitt 3.3 auf Seite 20 beschrieben benutzt die funktionale Programmierung ein anderes Schema zur Zerlegung (decomposition ) strukturierter Daten. Die Denition von Datenstrukturen wird typischerweise von der Implementierung der Operationen getrennt. Die Datenstrukturen werden mit algebraischen Datentypen deniert (siehe Abbildung 3.1 auf Seite 23), die Operationen als Funktionen implementiert, welche Mustervergleiche (pattern matching ) zur Zerlegung der strukturierten Daten verwenden.

102

6.3. Case Classes und Pattern Matching Scala bietet dem Programmierer die Mglichkeit, im funktionalen Stil mit strukturierten Daten zu arbeiten, jedoch ohne algebraische Typen in den Kern der Sprache aufzunehmen [OAC+ 04]. Stattdessen wird der Objekt-orientierte Abstraktionsmechanismus Klasse erweitert, um die Konstruktion strukturierter Daten zu erleichtern. Listing 6.3 zeigt, wie der abstrakte Datentyp aus Abbildung 5.2 auf Seite 90, der wiederum eine Implementierung der Datenabstraktion aus Abbildung 5.1 auf Seite 89 ist, in Scala umgesetzt werden kann. Zunchst wird eine abstrakte Superklasse ListAbstraktion deniert, dann zwei von ihr ableitende case classes NilClass und Adjoin, welche die Konstruktoren der Datenabstraktion aus Abbildung 5.1 auf Seite 89 implementieren. Dann werden die drei Beobachtungen der Datenabstraktion als Funktionen dargestellt, die jeweils ein pattern matching auf den case classes vornehmen. Den Funktionen wird ein Objekt der Superklasse ListAbstraktion bergeben, woraufhin sie je nach (ber Mustervergleich festgestellten) konkretem Subtyp des Parameters einen anderen Rckgabewert erzeugen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

object CaseClasses extends Application { abstract class ListAbstraction case class NilClass extends ListAbstraction case class Adjoin(head: Int, tail: List[Int]) extends ListAbstraction

def isNull(list: ListAbstraction): Boolean = list match { case NilClass() => true case Adjoin(head, tail) => false } def head(list: ListAbstraction): Any = list match { case NilClass() => new RuntimeException("Error") case Adjoin(head, tail) => head } def tail(list: ListAbstraction): AnyRef = list match { case NilClass() => new RuntimeException("Error") case Adjoin(head, tail) => tail } println("isNull(NilClass(): " + isNull(NilClass())) println("isNull(Adjoin(3, List(2,1))): " + isNull(Adjoin(3, List(2,1)))) println("head(NilClass()): " + head(NilClass())) println("head(Adjoin(3, List(2,1))): " + head(Adjoin(3, List(2,1)))) println("tail(NilClass()): " + tail(NilClass())) println("tail(Adjoin(3, List(2,1))): " + tail(Adjoin(3, List(2,1)))) }

Listing 6.3: Case Classes und Pattern Matching in Scala

103

6. Elemente Funktionaler Programmierung in Scala Die Ausgabe von Listing 6.3 auf der vorherigen Seite sieht folgendermassen aus:
1 2 3 4 5 6

isNull(NilClass(): true isNull(Adjoin(3, List(2,1))): false head(NilClass()): java.lang.RuntimeException: Error head(Adjoin(3, List(2,1))): 3 tail(NilClass()): java.lang.RuntimeException: Error tail(Adjoin(3, List(2,1))): List(2, 1)

Erklrungsbedrftig an dem Beispiel-Code fr case classes sind die beiden neuen Schlsselwrter case und match sowie die Art und Weise, wie Instanzen der case classes erzeugt werden. case Klassen mit diesem Schlsselwort case werden vom Scala-Compiler mit einigen zustzlichen Eigenschaften versehen. Eine Fabrikmethode (factory method ) mit dem Namen der Klasse wird hinzugefgt. Mit NilClass() und Adjoin(3,List(2,1) werden also neue Objekte der beiden case Klassen erzeugt. Alle Argumente der Parameterliste werden implizit mit dem Schlsselwort val versehen, so da sie als Felder der Klasse angesprochen werden knnen. Der Compiler erzeugt natrliche Implementierungen der Methoden toString, hashCode und equals fr die Klasse. match Der match Ausdruck kann als Generalisierung des switch Befehls in Java angesehen werden. Die Unterschiede sind: match gibt immer einen Wert zurck. Wenn es zu einer bereinstimmung der Muster kommt (wenn das bergebene Muster mit einem der Flle bereinstimmt) werden die nachfolgenden Flle nicht mehr abgearbeitet. Ein break Befehl ist also unntig. Wenn es zu keiner bereinstimmung mit einem Muster kommt (wenn kein Fall pat), wird die Ausnahme MatchError geworfen. Es mu also immer sichergestellt werden, da alle Flle behandelt werden. Zur Not mu als letztes ein Fall hinzugefgt werden, der einerseits auf jedes Muster zutrit, andererseits nichts tut (case _ =>). Mit dem match Ausdruck knnen verschiedene Arten von Mustern berprft werden: Platzhalter-Muster (wildcard-patterns) die an der Stelle des Platzhalters mit jedem beliebigen Element bereinstimmen, beispielsweise case _ oder case Adjoin(_, tail). Konstante Muster wie case 5, die nur mit sich selber bereinstimmen. Variablen-Muster, die wie Platzhalter mit jedem Objekt bereinstimmen, dieses aber dann an die Variable binden, bspw case variablenName.

104

6.3. Case Classes und Pattern Matching Konstruktor-Muster wie case Adjoin(3,List(2,1)), die nicht nur die bereinstimmung auf der Ebene des Klassentyps, sondern auch der Klassenargumente prfen knnen (sogenannte deep matches). Eine beliebig tiefe Verschachtelung von Konstruktor-Mustern ist mglich. Sequenz-Muster wie case List(0,_,_), die genau so funktionieren wie bei den case classes, nur da hier Sequenztypen wie Listen und Array als Muster verwendet werden. Tupel-Muster wie case (a, 2,_ ). Typisierte Muster wie case s: String oder case m: Map[_,_] knnen als komfortabler Ersatz fr Typ-Tests und Typ-Umwandlungen benutzt werden. Mittels pattern guards (Muster-Wchtern) knnen die Mustervergleiche noch prziser gemacht werden. Pattern Guards sind if-Klauseln, die an case-Statements angehngt werden, wie zB in case Adjoin(3, List(_,_)) if _ >= 0. Da beim Mustervergleich immer alle Flle behandelt werden mssen, wre es fatal, wenn der Vererbungshierarchie nachtrglich ein neuer Fall hinzugefgt wird und es beim Pattern Matching keinen globalen Fall wie case _ gibt, der dies auangen knnte. Um dieses Problem zu vermeiden, kann die Superklasse von case classes versiegelt (sealed) werden, so da keine weiteren Klassen abgeleitet werden knnen. Ein weiteres nicht nur fr Mustervergleiche sehr ntzliches Konstrukt von Scala ist der Option Typ, der entweder Some(x) oder None zurckgibt [OSV08]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

scala> val guitarStrings = Map(6 -> E,5 -> A,4 -> D,3 -> g, 2 -> b,1 -> e) guitarStrings: scala.collection.immutable.Map[Int,Char] = Map(2 -> b, 4 -> D, 6 -> E, 1 -> e, 3 -> g, 5 -> A) scala> def sound(strings: Option[Char]) = strings match { | | case Some(s) => s | | case None => "silence" | | } sound: (strings: Option[Char])Any scala> sound(guitarStrings get 5) res1: Any = A scala> sound(guitarStrings get 7) res2: Any = silence

Hoare hat die Idee einer Nullreferenz in Programmiersprachen als seinen billion dollar mistake bezeichnet [Hoa09]. In Java wird die Problematik von Nullreferenzen besonders deutlich, denn in der eigentlich Objekt-orientierten Sprache ist null ein Schlsselwort und kein Objekt, und auf einem Schlsselwort lassen sich keine Methoden aufrufen. Programmierer mssen sich also stndig gegen das Auftreten von Nullreferenzen absichern. Scala kann trotz der bekannten Probleme nicht auf null verzichten, da es auf der JVM (Java Virtual Machine) bzw der .net Plattform aufsetzt, die beide mit null arbeiten.

105

6. Elemente Funktionaler Programmierung in Scala Zudem gibt es unzhlige Java Bibliotheken, die Nullreferenzen verwenden. Scala besitzt aber mit der Option Klasse und ihren beiden Subklassen Some und None einen Mechanismus, der dem Programmierer auf elegante Weise hilft null zu vermeiden. Der Option Typ ist sehr geeignet, um NullPointerExceptions in Situationen zu vermeiden, in denen sonst entweder eine Referenz oder null zurckgegeben wird. Der Programmierfehler, eine Variable, die null sein kann, ohne vorherigen Test auf null zu verwenden, wird dadurch zu einem Typfehler, der vom Compiler moniert wird. Es wre also nicht mglich, den Parameter strings vom Typ Option[Char] wie einen einfachen Char zu verwenden.

6.4. Konstruktoren und Extraktoren


In Abschnitt 6.3 auf Seite 102 wurde beschrieben, wie mittels case classes strukturierte Daten erzeugt und dann ber Mustervergleiche zerlegt und analysiert werden knnen. Die case classes nehmen dabei die Rolle von constructors (Konstruktoren) ein [OSV08]. Extractors (Extraktoren) ermglichen es, Mustervergleiche auch unabhngig von case classes einzusetzen. Sie basieren auf dem dualen Methodenpaar apply wendet eine Klassendenition auf die aktuellen Klassenparameter an. Einfacher ausgedrckt kann man sagen, da apply mit den Klassenparametern eine Instanz der Klasse, also ein Objekt, zusammenbaut. unapply macht das Gegenteil von apply: es zerlegt ein Objekt in seine Bestandteile, auf denen dann Mustervergleiche durchgefhrt werden knnen. Ein Extraktor in Scala ist ein Objekt mit einer unapply Methode. Es hat oft auch die duale apply Methode, aber das ist nicht zwingend notwendig. Die unapply Methode kann auch auf bereits existierende Datentypen angewendet werden, und sie legt die konkrete Reprsentation der Daten nicht oen. Dadurch kann die Reprsentation eines Typs auch noch nachtrglich gendert werden, was bei case classes kaum mglich, ist wenn externe (Client-) Programme schon Mustervergleiche auf ihnen durchfhren. Dafr sind case classes einfacher und ezienter, und der Compiler kann Mustervergleiche auf Vollstndigkeit berprfen, wenn die Basisklasse der case classes versiegelt (sealed) wurde.

6.5. For Ausdrcke als Syntax fr Monaden


Scala hat nur eine Handvoll in die Sprache eingebauter Kontrollstrukturen [OSV08]:
1

if, while, for, try, match sowie Funktionsaufrufe

Wie bei funktionalen Sprachen handelt es sich hierbei um Ausdrcke, die einen Wert zurckgeben, und nicht um Befehle. Diese Rckgabewerte der Kontrollstrukturen knnen genauso weiterverwendet werden wie Rckgabewerte von Funktionen, wodurch die Notwendigkeit temporrer Variablen zur Speicherung der Ergebnisse von Kontrollstrukturen entfllt.

106

6.5. For Ausdrcke als Syntax fr Monaden Der for Ausdruck ist ein mchtiges und vielseitiges Werkzeug fr die Iteration in Scala. Er hat folgende allgemeine Form:
1

for ( seq ) yield expr

Wird yield expr weggelassen, handelt es sich um eine einfache for Schleife (loop). Ein Beispiel aus [OSV08] ist:
1 2 3 4 5

for { p <- persons // a generator n = p.name // a definition if (n.startsWith "To") // a filter } yield n

seq ist also eine Sequenz aus Generatoren (der Art pattern <- expression) sowie optionalen Denitionen und Filtern. Ein Generator <- bindet den Bezeichner links des Pfeils (im Beispiel p) an die aufeinanderfolgenden Elemente der rechts vom Pfeil stehenden Liste (im Beispiel persons).

Man kann mit einem for Ausdruck, der auch mehrere Generatoren hintereinander enthalten kann, typische Operationen von Datenbank-Abfragesprachen nachbilden, zB:
1 2 3 4 5

for ( b <- books; // semicolon separates multiple assignments a <- b.authors if a startsWith "Odersky") yield b.title

In den beiden letzten+ Beispielen wurden sowohl geschweifte als auch runde Klammern zur Denition eines for Ausdrucks verwendet. Beides ist mglich, den Unterschied erlutert folgendes Zitat von Wampler and Payne : for expressions may be dened with parentheses or curly braces, but using curly braces means you dont have to separate your lters with semicolons. Most of the time, youll prefer using curly braces when you have more than one lter, assignment, etc. (aus [WP09]) Der for Ausdruck basiert nur auf den Methoden map, flatMap und filter, er ist daher auf die groe Zahl von Datentypen anwendbar, auf die auch diese Methoden anwendbar sind. Die Funktionsweise von flatMap und filter wird in Listing 6.2 auf Seite 99 demonstriert. Es ist bemerkenswert, da das funktionale Konzept der Monaden (siehe Abschnitt 5.4.3 auf Seite 91) in der Objekt-orientierten Welt durch diese drei Methoden (sowie einen Instanz-Konstruktor bzw eine Factory-Methode) dargestellt werden kann. Da er quivalent zu der Anwendung dieser drei Methoden ist, kann der for Ausdruck als Syntax fr Monaden in Scala gelten. Diese quivalenz lt sich folgendermaen herleiten (nach [Pop10]):

107

6. Elemente Funktionaler Programmierung in Scala Ein for Ausdruck mit einem Generator
1

for { x <- e1 } yield e2

steht fr die Anwendung der map Methode auf die Liste e1:
1

e1.map (x => e2)

Abbildung 6.1 zeigt, wie ein for Ausdruck mit drei Generatoren in Methodenaufrufe von flatMap und map bersetzt wird. Wenn den Generatoren ein if guard folgt, wird dieser in den Aufruf einer filter Funktion bersetzt.

for { x <- eval(l) y <- eval(r) z <- evalOp(o,x,y) } yield z desugars to

eval(l).flatMap (x => eval(r).flatMap (y => evalOp(o,x,y).map (z => z)))

is equivalent to eval(l).flatMap (x => eval(r).flatMap (y => evalOp(o,x,y))) Abbildung 6.1.: Scalas for Ausdruck als Syntax fr Monaden (nach [Pop10])

108

Teil IV.

Typsysteme

7. Typsysteme
7.1. Typen vs. Typsysteme
Alle Programmiersprachen haben Typen (siehe Abschnitt 2.2.4 auf Seite 8), aber nur einige haben Typsysteme. Daher mu nach Cook [Coo90] unterschieden werden zwischen: Typen als (essentielle) Sprachelemente beim Design von Programmiersprachen. Hierunter fallen Reprsentationstypen, ADT und Klassen, also alle Typen, die sich auf Datenobjekte beziehen. Typen als (redundante) Annotationen bei der Deklaration von Variablen in einem Programm. Dies sind die sogenannten Deklarationstypen, die sich auf den Programmtext beziehen. Den Unterschied kann man in Scala recht gut demonstrieren, da das intelligente Typsystem von Scala es erlaubt, den Typ einer Variablen bei ihrer Denition auch mal wegzulassen - der Compiler erkennt den Typ automatisch. Man kann also in Scala schreiben
1

scala> var x = 7

was zunchst mal hnlich aussieht wie in Smalltalk (einer Sprache ohne Typsystem):
1

x := 7

In Scala ist var x = 7 aber nur eine Kurzschreibweise fr var x: Int = 7, was einem der Compiler auch sofort mitteilt, wenn man die Zuweisung im Scala-Interpreter eingibt:
1 2

scala> var x = 7 x: Int = 7

Beide Sprachen, Smalltalk und Scala, besitzen also einen Integer Typ, den man in Form von ganzen Zahlen (zB 7) einer Variablen (zB x) zuweisen kann. Der Unterschied liegt darin, da der Variablen x in Scala noch zustzlich die Annotation Int angeheftet wird. In Smalltalk knnte man derselben Variablen x auch noch einen String zuweisen.
1

x := "21"

In Scala ist das nicht mglich, da ber die Typannotation Int festgelegt wird, da x nur Werte vom Typ Integer aufnehmen darf und keine Strings oder andere Typen.

111

7. Typsysteme
1 2 3 4 5 6 7 8 9

scala> x = 14 x: Int = 14 scala> x = "21" <console>:5: error: type mismatch; found : java.lang.String("21") required: Int x = "21" ^

Die Typannotation Int knnte man auch weglassen (siehe Smalltalk) - das Programm wrde genauso funktionieren. Sie ist also redundante Information. Ihr Ziel ist es, die Qualitt eines Programmes zu erhhen, was auf verschiedenen Ebenen erreicht wird [SKS09]: Ezienteres Speicherlayout, da der Compiler den Typ einer Variablen kennt. Bessere Lesbarkeit eines Programmes durch die zustzliche Information ber den Typ von Variablen. Ezientere Programmausfhrung aufgrund von Compiler-Optimierungen, die nur mglich sind, wenn der Typ einer Variablen bekannt ist. Automatisches Finden von logischen Programmfehlern. Der letzte Punkt steht fr den Programmierer in der Regel im Vordergrund. Das Typsystem verhindert, da einer Variablen aus Versehen oder in Unkenntnis etwas zugewiesen wird, fr das die Variable von der Programmlogik her nicht gedacht war. Ohne ein Typsystem wrden solche Fehler erst zur Laufzeit bei der Benutzung des Programmes auftreten, also als ein Qualittsproblem der Software. Programmiersprachen lassen sich bezglich des Typsystems einteilen in: Dynamisch typisierte Sprachen wie Smalltalk, bei denen erst zur Laufzeit geprft wird, ob ein Objekt eine Nachricht versteht, ob es also eine Methode mit entsprechender Signatur besitzt, um einen Methodenaufruf abarbeiten zu knnen. Statisch typisierte Sprachen wie Scala, bei denen alle Variablen vom Programmierer mit einer Typannotation versehen werden und der Compiler keine Programme mit fehlerhaften Zuweisungen akzeptiert. Teilweise statisch typisierte Sprachen wie Java, bei denen die statische Typisierung unvollstndig ist. Wenn Typsysteme so vorteilhaft sind, warum besitzen dann nicht alle Programmiersprachen eines? Dazu ein Zitat von Alan Kay, dem Ernder von Smalltalk, aus dem Jahr 2003: Im not against types, but I dont know of any type systems that arent a complete pain, so I still like dynamic typing (nach [OSV08]).

112

7.2. Grundlagen Objekt-Orientierter Typsysteme Typsysteme haben also ihren Preis, und der Preis ist so hoch, da Koryphen wie Alan Kay lieber auf sie verzichten. Sie sind nicht unwesentlich dafr verantwortlich, da statisch typisierte Sprachen wortreicher, unproduktiver und unexibler sind als die dynamisch typisierten Sprachen. Zudem verkomplizieren Typsysteme sowohl das Leben der Sprachdesigner als auch der Software-Entwickler, und mit zunehmender Komplexitt einer Sprache lt oft ihre Konsistenz und Zuverlssigkeit nach. Typsysteme knnen also ungeheuer komplex werden und dabei trotzdem Schwchen und sogar innere Widersprche aufweisen. Sie knnen sogar zu streng sein und dadurch auch korrekte Programme zurckweisen. Da viele der Schwchen und Widersprche auf der Weiterentwicklung von Sprachen in Bereiche beruhen, fr die sie ursprnglich nicht gedacht waren (Beispiel Java), ist eine Neuentwicklung wie Scala immer auch eine Chance, neue Erkenntnisse der Forschung aufzugreifen und vielleicht ein Typsystem zu entwickeln, das auch Kritiker wie Alan Kay zufriedenstellt. Scalas Typsystem ist jedenfalls so intelligent, da Scala manchmal auch als the statically typed dynamic language bezeichnet wird [Pol07]. Damit soll ausgedrckt werden, da die automatische Typinferenz von Scala beim Schreiben und Lesen von Scala-Programmen den Eindruck erwecken kann, man htte es mit einer dynamisch typisierten Sprache ohne den syntaktischen Ballast der Typannotationen zu tun.

7.2. Grundlagen Objekt-Orientierter Typsysteme


Dieser Abschnitt orientiert sich stark an Steimann [SKS09]. Es werden einige wichtige Begrie aus dem Bereich Objekt-orientierter Typsysteme erlutert. Zuweisungskompatibilitt Der einer Variablen zugewiesene Typ und der Typ eines zugewiesenen Wertes oder Objektes mssen bereinstimmen. Die Eigenschaft ist nicht kommutativ, aber transitiv. Aus a:=b folgt also nicht zwangslug b:=a, aus a:=b und b:=c aber sehr wohl a:=c. Typerweiterung Der Typ auf der rechten Seite einer Zuweisung mu strukturquivalent zum Typen auf der linken Seite sein. In a:=b mu b also die gleichen Methoden anbieten wie a. b kann aber auch zustzliche Methoden anbieten, ohne da durch diese Typerweiterung die Zuweisungskompatibilitt in a:=b beeintrchtigt wird. Eine Zuweisung b:=a wird dadurch allerdings verhindert. Typeinschrnkung Wenn ein Typ unter Entfernen von Eigenschaften auf der Basis eines anderen Typs deniert wird, schrnkt er diesen unter Verlust der Zuweisungskompatibilitt ein. Auch durch die Redenition von Eigenschaften kann sich eine Typeinschrnkung ergeben. Typquivalenz Man unterscheidet: Strukturquivalenz : zwei Typen haben die gleiche Struktur (die gleichen Eigenschaften).

113

7. Typsysteme Namensquivalenz : zwei Typen haben die gleiche Struktur und den gleichen Namen. quivalente Typen sind gegenseitig zuweisungskompatibel. Typkonformitt Ein Typ, der alle Elemente eines anderen Typs enthlt, ist typkonform zu diesem. Man unterscheidet: Strukturelle Typkonformitt Nominale Typkonformitt Fr letztere mu ein Typ explizit als Erweiterung eines anderen Typs angegeben werden. Typkonformitt ist reexiv und transitiv. Typquivalenz ist ein Spezialfall der Typkonformitt und bedingt diese (was umgekehrt meistens nicht gilt). Bei Typsystemen spielt der Begri des Subtyps (und des Subtyping ) eine groe Rolle. Beim Subtyping leitet ein Subtyp von einem Supertyp ab. Er darf dann in einem Programm berall dort verwendet werden, wo der Supertyp erlaubt ist. Der Supertyp inkludiert den Subtyp, es besteht also eine Teilmengenbeziehung. Typerweiterungen durch den Subtypen erzeugen keine Typfehler, die erweiterten Subtypen unterscheiden sich allerdings inhaltlich von ihren Supertypen. Typeinschrnkungen (also die Redenition von Eigenschaften) werfen dagegen einige Probleme bezglich der Zuweisungskompatibilitt auf (siehe Abschnitt 7.3.4 auf Seite 118). Wenn in einem Programm ein Objekt eines Subtyps durch eine Variable eines Supertyps referenziert wird, knnen eventuelle zustzliche Eigenschaften des Objektes (Stichwort Typerweiterung) nicht ausgenutzt werden, da diese nicht Bestandteil des Supertyps sind. Hier schat die Typumwandlung Abhilfe, mit welcher der ursprngliche Typ der Variablen zum tatschlichen Typ des Objektes gendert wird (ein down cast vom Supertyp zum Subtyp). Da solche down casts im Gegensatz zu den selteneren up casts (Typumwandlung vom Subtyp zu einem Supertyp) nicht typsicher sind, sollten sie stets mit einem Typtest abgesichert werden. In Objekt-orientierten Sprachen stellt eine Klassendenition in der Regel auch eine Typdenition dar. Es handelt sich bei Klassen und Typen aber um zwei unterschiedliche Konzepte, die aufgrund ihrer strukturellen hnlichkeit in einem einzigen Sprachkonstrukt zusammengefat werden. Klassen sind aber eigentlich Container fr ausfhrbaren Code, Typen dienen der Erhhung der Sicherheit eines Programmes durch die Angabe redundanter Invarianten (Typannotationen). Typinformationen sorgen dafr, da logische Fehler bei Zuweisungen bereits beim Kompilieren erkannt werden. Das Auftreten anderer Programmfehler zur Laufzeit wird also durch das frhzeitige Signalisieren der Verletzung von Typinvarianten verhindert. Fr Java-Programmierer ist es oft schwierig zu erkennen, da Typen und Klassen unterschiedliche Konzepte sind und Typannotationen eigentlich nur redundante Informationen darstellen, die man auch weglassen knnte. Die Konzepte sind in ihrer Sprache einfach zu eng verwoben, und Typannotationen scheinen zum Kern der Sprache zu gehren.

114

7.3. Scalas Typsystem

7.3. Scalas Typsystem


7.3.1. Theoretische Grundlagen
Scala Typsystem beruht auf dem Obj Calculus von Odersky et al., von denen auch folgendes Zitat stammt: We design and study Obj , a calculus and dependent type system for objects and classes which can have types as members. Type members can be aliases, abstract types, or new types. The type system can model the essential concepts of Javas inner classes as well as virtual types and family polymorphism found in BETA or gbeta. It can also model most concepts of SML-style module systems, including sharing constraints and higher-order functors, but excluding applicative functors. The type system can thus be used as a basis for unifying concepts that so far existed in parallel in advanced object systems and in module systems. The paper presents results on conuence of the calculus, soundness of the type system, and undecidability of type checking. (aus [OCRZ03]) Der Object Calculus vereinigt die ausgefeilten Abstraktionsmglichkeiten der Modul-Systeme funktionaler Sprachen mit den Kompositionsmglichkeiten der Objektorientierten Sprachen. Scalas Typsystem ist nicht identisch mit dem Object Calculus, aber es basiert auf diesem. Es ist gleichzeitig auch eine Erweiterung von Javas ObjektModell [OCRZ03]. Klassen sind in Scala (im Gegensatz zu Obj ) keine Objekte erster Klasse, die als Argumente in einem Programm herumgereicht werden knnen. Bestimmte Konstrukte in Scala wie Funktionen hherer Ordnung, Generics und Pattern Matching lassen sich auch erst nach einer bersetzung auf Obj abbilden, whrend es bei anderen eine direkte Korrespondenz gibt. An dieser Stelle sollen nur die fr funktionale Modul-Systeme wichtigen Begrie Modul, Signatur und Funktor (im Kontext der funktionalen Sprache SML) erlutert und in Beziehung zu den Begrien Objekt, Trait und Klasse aus der Objekt-orientierten Welt gesetzt werden. Zudem werden die wesentlichen Bestandteile von Scalas Typsystem vorgestellt. Fr das Modul-System von SML1 spielen folgende Begrie eine wichtige Rolle [Mil97]: Modul (in SML auch Struktur ) genannt, ist eine logische Einheit, in der Typen, Ausnahmen (exceptions), Werte und andere Module (substructures ) zusammengefat werden. Signatur ist eine Schnittstelle (interface), die als Typ eines Moduls fungiert. Sie speziziert die Namen aller vom Modul angebotenen Dienste, die Eigenschaften der
1

SML (Standard ML) ist eine moderne Variante der in den frhen 70ern an der University of Edinburgh entstandenen funktionalen Programmiersprache ML (Meta Language). SML unterscheidet sich von anderen Sprachen durch seine formale Spezikation in Form von Typisierungsregeln und operationalen Semantiken [Mil97].

115

7. Typsysteme enthaltenen Typen, die Typen der enthaltenen Werte sowie die Signaturen der enthaltenen Untermodule (substructures). Typen, deren Denition nicht ber die Signatur exportiert wird, sind abstrakte Typen. Funktor ist eine Funktion, die eine (bzw mehrere) als Argument(e) bergebene Struktur(en) auf eine andere Struktur abbildet. Funktoren werden zur Implementierung generischer Datenstrukturen und Algorithmen verwendet. Der Obj Calculus untersttzt die meisten Konzepte eines SML-Modulsystems [OCRZ03]. Dabei werden folgende Gleichsetzungen vorgenommen: Object = M odule Object type = Signature Class = M ethod = F unctor Auch Scalas Typsystem folgt diesem Ansatz, wobei allerdings auf der syntaktischen Ebene eine Unterscheidung zwischen Klassen und Methoden aufrechterhalten wird. Objekte bernehmen in Scala als Singleton-Objekte der obersten Ebene die Rolle von Modulen, Klassen werden mit Funktoren gleichgesetzt und Signaturen mit Traits [CGLO06].

7.3.2. Typen als gleichberechtigte Sprachelemente


In Scala sind Typen gleichberechtigt zu Methoden und Feldern, treten also mehr als eigenstndiges Konzept in Erscheinung [VS09]. Fr Methoden, Felder und Typen gelten die gleichen Konstruktionsregeln, dh Abstraktion kann sowohl durch Parametrisierung als auch durch abstrakte Mitglieder (abstract members) realisiert werden. Tabelle 7.1 gibt eine Gegenberstellung der Abstraktionsmglichkeiten in Scala und Java, Tabelle 7.2 zeigt Code-Beispiele fr Abstraktionen in Scala. Java AM P P Scala P AM P AM P AM

Methoden Felder Typen

Tabelle 7.1.: Mglichkeiten der Abstraktion in Java und Scala. P steht fr Parameterization, AM fr Abstract Members Scala Methods Fields Types Parameterization class C(funct: Function1[Int,Int]) class C(eld: String) class C[T] (eld: String) Abstract Members def funct(in: Int): Int val eld: String class C { type t; val v: t }

Tabelle 7.2.: Code-Beispiele fr Abstraktion in Scala.

116

7.3. Scalas Typsystem

7.3.3. Funktionale Typabstraktion


Der funktionale Stil der Typabstraktion beruht auf der Parametrisierung von Typen. Diese kann auf Klassen und Traits, aber auch auf Methoden angewandt werden. Das Prinzip ist dabei ganz hnlich wie bei den gewhnlichen bergabeparametern von Methoden:
1 2 3 4 5 6 7 8

scala> def sum(x: Int, y: Int): Int = x + y sum: (x: Int,y: Int)Int scala> sum(3,4) res0: Int = 7 scala> sum(7,8) res1: Int = 15

Hier wird von den Werten der bergabeparameter abstrahiert. Anstatt fr x und y konkrete Werte anzugeben, wird jeweils nur ihr Typ speziziert (Int). Die Methode sum() funktioniert also mit allen mglichen Werten fr die bergabeparameter x und y, solange diese dem angegebenen Typ entsprechen. Typabstraktion geht einen Schritt weiter als diese Abstraktion von Werten. Hier wird auch noch vom Typ der Methodenparameter abstrahiert. Listing 7.1 zeigt eine Klasse und eine Methode die mit dem abstrakten Typ T parametrisiert sind. Klasse und Methode sind also generisch, sie lassen sich fr verschiedene Typen einsetzen, ohne zustzlichen Code schreiben zu mssen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

object Parameterized extends Application { class GenericClass[T](param1: T, param2: T) { def buildList(var1: T, var2: T): List[T] = List(param1,param2,var1,var2) } val myObject1 = new GenericClass[Int] (3,4) println(myObject1.buildList(5,6)) val myObject2 = new GenericClass[String] ("hello","world") println(myObject2.buildList("what\s","up?")) val myObject3 = new GenericClass[AnyRef] ("hello",List(1,2,3)) println(myObject3.buildList("are reference types just like ", () => 4 + 3 )) val myObject4 = new GenericClass[Any] ("hello",List(1,2,3)) println(myObject4.buildList("are subclasses of Any just like ", 4 + 3 )) }

Listing 7.1: Denition generischer Klassen und Methoden in Scala mittels TypParametrisierung Die Ausgabe von Listing 7.1 sieht folgendermaen aus:
1 2 3 4

List(3, 4, 5, 6) List(hello, world, whats, up?) List(hello, List(1, 2, 3), are reference types just like , <function0>) List(hello, List(1, 2, 3), are subclasses of Any just like , 7)

117

7. Typsysteme Zunchst werden der Klasse Int-Klassenparameter, dann String-Klassenparameter bergeben und die Methode buildList mit Methodenparametern vom gleichen Typ aufgerufen. Ergebnis ist eine List[Int] bzw List[String]. Wenn fr T der Supertyp aller Referenztypen AnyRef eingegeben wird, kann die Liste aus Strings, Listen und Funktionsliteralen zusammengebaut werden, denn es sind alles Referenztypen. Beim Versuch, einen Wertetyp wie zB den Integer 7 als Methodenparameter fr T einzusetzen, wird eine Fehlermeldung ausgegeben. Wenn als Klassenparameter Any statt AnyRef bergeben wird, knnen auch Wertetypen wie 4 + 3 als Methodenparameter bergeben werden. Durch Verwendung von scala.Nothing (siehe Abschnitt 4.5.2 auf Seite 60) als Typparameter kann eine Klasse List[Nothing] als Typ fr Nil deniert werden [OAC+ 04]. Da Nothing Subtyp aller anderen Typen in Scala ist, und Scalas Listen kovariant sind (siehe Abschnitt 7.3.4), ist Nil damit Instanz aller mglichen List[T] und kann ganz allgemein verwendet werden, beispielsweise zum Aufbau einelementiger Listen:
1 2 3 4 5 6 7 8

scala> 1 :: Nil res1: List[Int] = List(1) scala> a :: Nil res2: List[Char] = List(a) scala> "String" :: Nil res3: List[java.lang.String] = List(String)

7.3.4. Typschranken und Varianzen


Die funktionale Variante der Typabstraktion in Scala ist enorm exibel. Es knnen nicht nur Typparameter angegeben werden - sie knnen auch noch mit Typschranken und Varianzen versehen werden [Boe06]. Obere und untere Typschranken dienen der Eingrenzung mglicher Typen fr Typparameter:
1 2

[T <: A] // Typvariable T verweist auf einen Subtyp von A [T >: A] // Typvariable T verweist auf einen Supertyp von A

Eine generische Methodendenition wie def buildList [T <: AnyVal] wrde also aufgrund der oberen Typschranke sicherstellen, da die Belegung des Typparameters T nur dann zulssig ist, wenn es sich um einen Wertetyp handelt (einen Subtyp von AnyVal). Eine Denition wie def buildList [T <: Ordered] wrde sicherstellen, da T nur mit Typen belegt werden kann, die entweder von Ordered erben oder das Trait Ordered implementieren. Wampler and Payne geben ein interessantes Beispiel aus Scalas Klassenbibliothek fr den Einsatz einer unteren Typschranke [WP09]:
1 2 3 4 5

class List[+A] { ... def ::[B >: A](x: B): List[B] = new scala.::(x, this) ... }

118

7.3. Scalas Typsystem Zum besseren Verstndnis dieses Beispiels soll zunchst angemerkt werden, da hier der Bezeichner :: in zwei verschiedenen Bedeutungen auftritt: 1. Die Methode :: wird in der Klasse List[+A] deniert. 2. Die Klasse :: leitet in Scala von der Superklasse List ab. Sie ist Rckgabewert der Methode ::, die eine neue Liste vom Typ List[B], genauer gesagt vom Typ scala.::, erzeugt. Die Methode :: nimmt also einen Parameter x vom Typ B entgegen. Sie erzeugt eine neue Liste, indem sie mit x und this (also dem existierenden Listenobjekt auf dem die Methode :: aufgerufen wird) als Klassenparametern eine neue Instanz der Klasse scala.:: erzeugt. Diese macht dann x zum head und this zum tail der neuen Liste. Die untere Typschranke [B >: A] und der Rckgabewert List[B] erzwingen nun, da der Typ der neu erzeugten Liste der erste gemeinsame Supertyp von x und this in der Klassenhierarchie ist. Das folgende Beispiel zeigt, da der erste gemeinsame Supertyp von String und Int der Typ Any ist (siehe Abschnitt 4.5.2 auf Seite 60). Man erkennt auch, da :: eine rechtsassoziative Methode ist, die ihre Argumente von rechts nach links bindet, im Gegensatz zum standardmigen links-assoziativen Binden. Das Element rechts von :: mu also eine Liste (oder nil) sein, whrend das Element links von :: einen beliebigen Typ haben darf [WP09].
1 2 3 4 5 6 7 8 9 10 11

scala> val Skatkarten1 = List("Bube", "Dame", "Koenig", "As") Skatkarten1: List[java.lang.String] = List(Bube, Dame, Koenig, As) scala> 10 res0: Int = 10 scala> val Skatkarten2 = 10 :: Skatkarten1 Skatkarten2: List[Any] = List(10, Bube, Dame, Koenig, As) scala> val Skatkarten3 = 6 :: 7 :: 8 :: 9 :: Skatkarten2 Skatkarten3: List[Any] = List(6, 7, 8, 9, 10, Bube, Dame, Koenig, As)

Die Methode :: wird in der Klasse List[+A] deniert. Der Typparameter der Klasse ist also mit einem Bezeichner fr die Varianz des Typs versehen, in diesem Fall ein +, das fr Kovarianz steht. Varianzen beeinussen das Verhalten von Subtypbeziehungen in Strukturen mit Typparametern. Man unterscheidet (unter der Annahme, da die Richtung der Vererbungshierarchie von der abgeleiteten Superklasse zur ableitenden Subklasse verluft) Kovarianz Subtypbeziehung in Richtung der Vererbungshierarchie Kontravarianz Subtypbeziehung entgegen der Richtung der Vererbungshierarchie In Scala wird Kovarianz durch ein + vor dem Typparameter ausgedrckt. Fr die generische Klasse class MyClass[+T] gilt dann, da MyClass[T] ein Subtyp von MyClass[S]

119

7. Typsysteme ist, wenn T ein Subtyp von S ist (Kovarianz ). Dies ist ein natrliches Verhalten und die bliche Form der Typvarianz in der Programmierung [SKS09]. Fr die generische Klasse class MyClass[-T] gilt, da MyClass[T] ein Supertyp von MyClass[S] ist, wenn T ein Subtyp von S ist (Kontravarianz ). Kontravarianz ndet sich vor allem bei der Denition von Typen fr Methoden-Argumente. Scalas Typsystem stellt sicher, da die Varianz-Annotationen nur an geeigneten Stellen im Code erscheinen [OAC+ 04]. Tabelle 7.3 listet auf, welche Varianz-Annotation in welcher Position gestattet ist: Kovariante Position Typen unvernderlicher Felder Rckgabewerte von Methoden Kontravariante Position Typen von Methoden-Argumenten Obere Typparameter-Schranken

Tabelle 7.3.: Erlaubte Positionen kovarianter und kontravarianter Varianz-Annotationen Ein gutes Beispiel fr die Anwendung von Varianz-Annotationen sind die Funktionstypen aus Scalas Klassenbibliothek. Scala deniert ein Trait fr jede FunctionN mit N von 0 bis 22 [WP09], zB Function3:
1 2 3 4

trait Function3[-T1, -T2, -T3, +R] extends AnyRef { def apply(v1:T1, v2:T2, v3:T3):R ... }

Die ersten beiden Zeilen des Traits scala.Function3 zeigen in bereinstimmung mit Tabelle 7.3, da die Typen der drei Funktionsparameter kontravariant deniert werden und der Typ des Rckgabewertes kovariant deniert wird:

7.3.5. Objekt-Orientierte Typabstraktion


Neben der funktionalen Variante der Typvarianz gibt es in Scala auch eine Objektorientierte Form der Typparameterisierung. Dabei wird eine abstrakte Klasse erzeugt, die abstrakte Felder fr Typen und Klassenparameter enthlt. Diese werden bei der Erzeugung von Instanzen der Klasse durch konkrete Denitionen implementiert. Das folgende Beispiel lehnt sich an Wampler and Payne an [WP09].
1 2 3 4 5 6 7 8 9 10 11

object abstrTypes3 extends Application { abstract class ZipCode { type In val zipCode: In def printZipCodeMinusOne: Unit } class IntZipCode(val zipCode: Int) extends ZipCode { type In = Int def printZipCodeMinusOne = println("IntZipCode: " + (zipCode - 1)) }

120

7.3. Scalas Typsystem


12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

class StringZipCode(val zipCode: String) extends ZipCode { type In = String def printZipCodeMinusOne = println("StringZipCode as Int: " + (zipCode.toInt - 1)) } val iZip = new IntZipCode(123456) val sZip = new StringZipCode("234567") val abstrZip = new ZipCode {type In = Int; val zipCode = 654321; def printZipCodeMinusOne = println("ZipCode: " + (zipCode - 1 )) } iZip.printZipCodeMinusOne sZip.printZipCodeMinusOne abstrZip.printZipCodeMinusOne }

Die Ausgabe des obigen Beispiels sieht folgendermaen aus:


1 2 3

IntZipCode: 123455 StringZipCode as Int: 234566 ZipCode: 654320

Der Typ In in der abstrakten Klasse ZipCode ist ein abstrakter Typ, der in den beiden von ZipCode abgeleiteten konkreten Klassen IntZipCode und StringZipCode mit einem konkretem Typ deniert wird (Int bzw String). Die Variable zipCode wird in ZipCode mit dem abstrakten Typ In deklariert. Je nach konkreter Denition von In kann sie in den abgeleiteten konkreten Klassen dann zB als Int oder String benutzt werden. Die beiden konkreten Klassen sind aber nicht unbedingt notwendig, um ZipCode benutzen zu knnen. Wie man in dem Beispiel sieht, knnen die konkreten Denitionen der abstrakten Felder von ZipCode auch direkt bei der Erzeugung einer neuen Instanz als Argumente angegeben werden.

7.3.6. Verbesserungen gegenber Javas Typsystem


Scalas Typsystem ist nicht nur mchtiger und intelligenter als das von Java, es adressiert auch einige seiner Schwachstellen. Generell wre es beim berschreiben einer Methode in einem Subtypen sinnvoll, die Eingabeparameter kontravariant und die Ausgabeparameter kovariant zu denieren, da so die Zuweisungskompatibilitt erhalten bleibt [SK09]. Da Subtypen Typerweiterungen vornehmen knnen, wre es problematisch, wenn die vom Supertyp bernommenen und eventuell redenierten Methoden auf solche neuen Eigenschaften des erweiterten Subtyps des Eingabeparameters zugreifen knnten. Wenn der Subtyp an einer Stelle im Programm den Supertyp ersetzt, aber keinen erweiterten Eingabeparameter bergeben bekommt, gibt es einen Fehler. Analog lt sich begrnden, warum die Ausgabeparameter redenierter Methoden von Subtypen kovariant sein sollten. Sie drfen ruhig erweitert werden, da die aufrufenden Methoden ohnehin nur den Supertyp erwarten, aber sie mssen als

121

7. Typsysteme Subtypen diesen Supertypen ersetzen knnen. Beim Entwurf von Java wurde eine Snde begangen, die weitreichende Folgen hatte: Java-Arrays wurden kovariant gemacht [VS09]. String[] ist in Java also ein Subtyp von Object[]. Warum ist das problematisch, wenn in Scala List[String] ein Subtyp von List[ScalaObject] bzw List[AnyRef] sein darf? Weil Javas Arrays vernderlich sind, und dieses kovariante Verhalten bei vernderlichen Typen nicht typsicher ist. Es kann in Java also array store exceptions geben, und die Idee generischer Kollektionen lt sich auf Java-Arrays nicht anwenden. Aus diesem Grund sind alle vernderbaren Typen in Scala invariant [WP09]. Die Denition kovarianter unvernderlicher Typen ist aber mglich (siehe Abschnitt 7.3.4 auf Seite 118). Da die Denition generischer Typen in Scala durch die Entwickler der Sprachbibliotheken und nicht durch die Anwendungsprogrammierer erfolgt, kann der Compiler in die Lage versetzt werden, ungltige Typzuweisungen zu erkennen und somit die typsichere Verwendung kovarianter (unvernderlicher) Kollektionen zu garantieren. In Scala knnen, im Gegensatz zu Java, Typen strukturell beschrieben werden, indem nur festgelegt wird, welche Elemente ein Typ haben mu um an einer bestimmten Stelle im Programm benutzt werden zu knnen [VS09]. Das entspricht dem sogenannten Ducktyping in dynamischen Sprachen wie Ruby. Wenn von einer Ente verlangt wird wie eine Ente laufen und quaken zu knnen, dann ist jeder Typ, der diese Eigenschaften aufweist, typkonform zum Typ Ente. Neben den strukturellen Typen gibt es in Scala auch existentielle Typen, die aber fr den Anwendungsprogrammierer weniger interessant sind und vor allem die Kompatibilitt mit Java sicherstellen [VS09]. Sie haben drei wichtige Einsatzgebiete: Modellierung von Javas wildcards. Modellierung von Javas primitiven Typen. Modellierung der Vorgnge in der JVM aus Sicht von Scalas Typsystem. Existenzielle Typen knnten also benutzt werden, um als Anwendungsprogrammierer in Scala generische Typen im Stile von Javas wildcards zu denieren. Dies wird aber von Odersky ausdrcklich nicht empfohlen [VS09]. In Scala sollen, wie oben gesagt, Bibliotheksautoren generische Typen denieren, und nicht die Nutzer der Sprachbibliotheken.

122

Teil V.

Objekt-Funktionale Programmierung

8. Warum Objekt-Funktionale Programmierung?


8.1. Java-Frust
Der Status Quo in der industriellen Software-Entwicklung ist seit zwei Jahrzehnten das Objekt-orientierte Paradigma. Die wichtigsten Sprachen in der System-Entwicklung waren (nach einem kurzen Smalltalk-Hype) C++ und Java, beides Mitglieder der CSprachenfamilie. Obwohl Smalltalk unter den wenigen Eingeweihten weiterhin einen legendren Ruf in Bezug auf Entwicklerproduktivitt besa (... in Smalltalk wre das eine Zeile Code gewesen), hatten viele Java-Entwickler lange Zeit keine wirklichen Vergleichsmglichkeiten. Smalltalk und funktionale Sprachen waren unter den jungen Entwicklern wenig bekannt, Objekt-orientierte Programmierung war unzweifelhaft ein Fortschritt gegenber der imperativen Programmierung, und im direkten Vergleich zu seinem Vorgnger C++ schnitt Java fast immer gut ab. Mit zunehmender Popularitt der in den 90er Jahren entwickelten Skript-Sprachen Python und Ruby wuchs auch das Bewutsein unter den Programmierern, da die recht konventionelle Sprache Java noch nicht das globale Optimum in der Evolution der Programmiersprachen darstellen kann. Da beide Sprachen sich viel bei den funktionalen Sprachen und (insbesondere Ruby) bei Smalltalk abgeschaut haben sowie viele moderne Konzepte der Programmierung bernommen haben, erschien Java im Vergleich auf einmal wortreich, unproduktiv, inkonsistent und arm an Eigenschaften. Fr Java sprachen dagegen weiterhin die statische Typsicherheit, die ausgereiften Entwicklungswerkzeuge, die Geschwindigkeit und Sicherheit der JVM sowie die Industriestandards, die sich in der Java-Welt entwickelt hatten. Als sich die JVM als Laufzeitumgebung fr andere Sprachen als Java nete und die Java-IDEs mittels neuer Plugins nachzogen, konnten die moderneren (Skript-) Sprachen einen Groteil ihrer Dezite ausgleichen und die Sprache Java etwas in die Defensive drngen. Die Industrie reagiert allerdings konservativ. Obwohl viele Entwickler begeistert mit den neuen Sprachen experimentieren, konzentrieren sich die Stellenanzeigen fr ernsthafte Programmierung weiterhin auf Java [Dua11]. Mit Scala (Objekt-funktional) und Clojure (rein funktional) sind in den letzten Jahren aber zwei neue Sprachen auf der JVM populr geworden, die durchaus das Potential haben Java vom Thron zu stoen. Whrend Clojure sich radikal von der Objekt-orientierten Programmierung und dem Konzept des vernderbaren Zustandes als Regelfall abwendet und dadurch besonders fr die nebenluge Programmierung geeignet ist [VS10], ermglicht Scala einen sanfteren bergang fr Java-Programmierer und bietet neue Mglichkeiten bei der typsicheren Modellierung von Komponentensystemen [WP09]. Anfang 2011 wurde Scala von der Technologierma Thoughtworks von der Kategorie

125

8. Warum Objekt-Funktionale Programmierung?


assess in die Kategorie trial hochgestuft. Dazu wurde folgende Begrndung gegeben:

In the previous radar we had two JVM-based functional programming languages, Clojure and Scala, in our Assess category. We had expressed a slight preference for Clojure because it is the smaller and more focused language. Since the last radar we have realized that the wider applicability of Scala makes it more approachable for enterprise developers, and we have witnessed great successes in the adoption of Scala. Consequently we have moved Scala into our Trial category. Pay careful attention to the idiomatic use of Scala if it is introduced to a new team to avoid Java without semicolons or Perl styles. (aus [Tho11]) Abbildung 8.1 zeigt die (naturgem subjektive) Einstufung von Scala in die Kategorie der Anwrter auf eine Empfehlung fr den Produktiveinsatz. Bemerkenswert ist, da Thoughtworks nicht nur empehlt den neuen Shooting Star Clojure unter Beobachtung zu haben, sondern auch den Niedergang der Programmiersprache Java. Adopt Ruby JRuby JavaScript1 C# 4.0 Trial HTML 5 Groovy Scala HAML DSLs SASS, SCSS, LESS Assess Clojure F# Java language2

Abbildung 8.1.: Die Technologierma Thoughtworks stuft Scala in die Kategorie trial ein (aus [Tho11]) Natrlich ist nur ein Teil der Java-Entwickler von Javas Eigenschaften frustriert. Der andere Teil hat vielleicht ein inniges emotionales Verhltnis zur Sprache aufgebaut und ist froh nicht mit neuen und teilweise komplizierten Konzepten behelligt zu werden. Wenn Java auch noch alle Anforderungen eines Unternehmens erfllt und die eher geringe Dynamik der Sprachentwicklung dem Bedrfnis der Investitionssicherung entgegenkommt, erscheint ein Wechsel zu Scala nicht unbedingt erforderlich, zumal die Werkzeuguntersttzung von Scala noch lange nicht mit der von Java mithalten kann. Bis zur Version 2.8 mute man die Qualitt der IDE-Plugins fr Scala eher als unterirdisch bezeichnen. Durch die wesentlich verbesserte Plugin Architektur des scalac Compilers, die es Plugins ermglicht in allen Phasen des Kompilierprozesses auf den (Byte) Code zuzugreifen, soll es aber rasante Fortschritte in diesem Bereich gegeben haben [WP09]. Die drei wichtigsten IDEs im Java-Umfeld, Eclipse, NetBeans und IntelliJ IDEA, sollen mittlerweile solide Scala-Plugins besitzen. Sehr beachtenswert und durch den Autor erprobt ist auch ENSIME (ENhanced Scala Interaction Mode for Emacs), ein im Rahmen von Googles Summer of Code von Aemon Cannon entwickeltes EmacsModul, das ebenfalls mit dem Scala-Compiler kommuniziert und zu eher IDE-typischen Dingen wie Code-Inspektion, Debugging, Refactoring und inkrementeller Kompilierung

126

8.2. Neue Lsungen fr alte und neue Probleme fhig ist [Can11]. Der Entwickler von ENSIME hat auch einen ausfhrlichen Blog-Beitrag ber seine Erfahrungen mit dem neuen Scala-Compiler verfat [Can10]. Ansonsten, wenn der Bedarf auf und das Bedrfnis nach Weiterentwicklung gegeben ist, bietet Scala die groe Chance, ohne allzu groes Risiko und unter Wahrung der bisher in Java gettigten Investitionen die Entwicklerproduktivitt und vielleicht auch motivation zu steigern, und mit einer expressiven Programmiersprache auf einem hheren Abstraktionsniveau Software zu produzieren, die mindestens so (typ-) sicher wie JavaCode und hnlich performant ist.

8.2. Neue Lsungen fr alte und neue Probleme


Scala bzw die Objekt-funktionale Programmierung adressiert einige zentrale Probleme der Softwareindustrie, die teilweise schon sehr alt sind, teilweise erst aufgrund neuer Entwicklungen zu Tage getreten sind: Komponenten Die Softwareindustrie trumt seit langem davon den Produktionsproze der Hardwareindustrie kopieren zu knnen: unabhngige Hersteller produzieren genormte Komponenten, die in beliebiger Kombination zu funktionierenden Systemen zusammengefgt werden knnen [OZ05]. Die Objekt-orientierte Programmierung konnte den groen Erwartungen in diesem Bereich nicht gerecht werden. Auch heute noch werden die meisten Softwaresysteme eher handwerklich-knstlerisch hergestellt als industriell. Services Service-Orientierung ist ein in der Entstehung begrienes Programmierparadigma, das groen Einu auf die Programmierung haben wird [EMO06]. Es wird durch zwei starke Trends motiviert: Verteilte und heterogene Programmierumgebungen (viele Maschinen statt einer) Grbere, standardisierte und semi-strukturierte Datenbertragungsformate (XML statt proprietrer Formate). Skalierung Heutzutage werden Anwendungen nicht mehr vorrangig durch den Kauf eines leistungsstrkeren Prozessors an eine hhere Last angepat, sondern durch die Verteilung der Anwendung auf mehrere Prozessoren mit der gleichen Leistung. Diese Art der horizontalen anstatt vertikalen Skalierung hat groen Einu auf die programmiertechnischen Anforderungen an eine Software-Anwendung. Die parallele oder nebenluge Programmierung wird in den Objekt-orientierten MainstreamSprachen nur ungengend untersttzt und stellt dadurch hohe Anforderungen an die Programmierer. Das Ziel hier Abhilfe zu schaen manifestiert sich schon im Namen von Scala: a scalable language [HO07]. In den folgenden Abschnitten werden diese Probleme und ihre Objekt-funktionalen Lsungsanstze genauer betrachtet.

127

8. Warum Objekt-Funktionale Programmierung?

8.3. Komponenten-basierte Programmierung


8.3.1. Was sind Komponenten?
Komponenten sind Programmteile, die von anderen Programmteilen oder ganzen Anwendungen benutzt werden [OZ05]. Es kann sich um Module, Klassen, Bibliotheken, Frameworks, Prozesse oder Web Services handeln. Sie knnen wenige Zeilen oder tausende Linien Code enthalten und ber verschiedene Mechanismen (wie Aggregation, Parametrisierung, Vererbung, entfernte Aufrufe oder Nachrichtenversand) mit anderen Komponenten verbunden werden. Software-Komponenten sollten (hnlich wie Hardwarekomponenten) wiederverwendbar sein. Fr sichere Wiederverwendung braucht eine Komponente eine genau spezizierte Schnittstelle, ber die sie angesprochen werden kann. Fr exible Wiederverwendung sollten die Anzahl harter Links zwischen abhngigen Komponenten, also ihre starke Kopplung mittels direkter Links von einer Komponenten zur anderen, minimiert werden.

8.3.2. Abstraktionen fr Komponentensysteme


Insbesondere die statisch typisierten Mainstream-Sprachen wie Java bieten nur wenig Untersttzung fr die Abstraktion und Komposition von Komponenten. Dazu bemerken Odersky and Zenger folgendes: "While these languages [Java, C#] oer some support for attaching interfaces describing the provided services of a component, they lack the capability to abstract over the services that are required. Consequently, most software modules are written with hard references to required modules. It is then not possible to reuse a module in a new context that renes or refactors some of those required modules. (aus [OZ05]) Obwohl Java Interfaces anbietet, um die von einer Komponente angebotenen Dienstleistungen zu beschreiben, fehlen Mglichkeiten von den von einer Komponente bentigten Dienstleistungen zu abstrahieren. Eine Programmiersprache braucht drei Arten von Abstraktionen um die Entwicklung von Softwaresystemen ohne statische Daten und harte Referenzen zu untersttzen [OZ05]: Abstrakte Typen als Felder Scala untersttzt sowohl die funktionale Form (Parameterisierung, siehe Abschnitt 7.3.3 auf Seite 117) als auch die Objekt-orientierte Form (abstrakte Typen oder Werte als Felder, siehe Abschnitt 7.3.5 auf Seite 120) der Abstraktion. Letztere ist fundamental fr die Komposition bei der Programmierung im Groen. Es ist mglich, abstrakte Klassen zu denieren, die keine Klassenparameter, aber abstrakte Typen und abstrakte Werte als Felder haben. Die Instanzen einer solchen Klasse mssen diese abstrakten Felder implementieren, also einen konkreten Typ bzw einen konkreten Wert fr ein Feld angeben. Da in Scala Typschranken angegeben werden knnen (T <: oder T >:), lt sich die Typinformation fr solche abstrakten Felder dabei noch genauer spezizieren.

128

8.3. Komponenten-basierte Programmierung Diese Objekt-orientierte Form der Abstraktion ermglicht den Komponenten in Scala Referenzen auf von ihnen bentigte Module zu haben, die sich nur auf einen abstrakten Typ beziehen und ganz unabhngig davon sind, an welchen konkreten Typ bzw Wert die abstrakten Felder dieses Typs letztendlich gebunden werden. Modulare mixin Komposition Scalas Klassen knnen von einer Klasse oder einem Trait ableiten (extends) und beliebig viele weitere Traits einbinden (with). Dies ist eine spezielle Form der Mehrfach-Vererbung mit einer Superklasse und einer beliebig groen Anzahl von mixins (siehe Abschnitt 4.4.3 bis 4.4.5 auf den Seiten 5054). Die eingebundenen Basis-Klassen werden linearisiert, wodurch eine eindeutige Prferenz, eine lineare Abfolge, zwischen ihnen hergestellt und damit das Problem der mglichen Namenskonikte gelst wird. Beim Durchsuchen einer Linearisierung wird der erste Treer fr einen Namen akzeptiert, also die am meisten spezialisierte Implementierung. Das Einbinden von Traits als mixins in Scala-Klassen mittels with ist Scalas Form der Komposition von Objekten. selftype Annotationen Normalerweise lt sich mit this innerhalb einer Klasse oder eines Traits auf den umschlieenden Typ verweisen. Beispielsweise entspricht this.myMethod() innerhalb von val myObject = new MyClass dem folgenden Methodenaufruf von auerhalb der Klasse: myObject.myMethod(). Die Referenz auf this mu in der Klasse in der Regel gar nicht angegeben werden, auer es knnte zu Namenskollisionen kommen oder der Programmtext soll fr menschliche Leser verstndlicher gemacht werden [WP09] . In Scala lassen sich selftype annotations dazu verwenden, um Aliase fr this zu erzeugen oder damit zustzliche Typerwartungen fr this zu erzeugen. Da abstrakte Typen und der Mixin-Mechanismus bereits beschrieben wurden, werden hier nur Beispiele fr den Einsatz von selftype annotations gegeben. Das erste Beispiel stammt aus Wampler and Payne und veranschaulicht, wie aus einer inneren Klasse mittels einer selftype annotation, die als alias einer ueren Klasse fungiert, auf den Typ der ueren Klasse zugegrien werden kann [WP09]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

object Comp1 extends Application { class C1 { self => def talk(message: String) = println("C1.talk: " + message) class C2 { class C3 { def talk(message: String) = self.talk("C3.talk: " + message) } val c3 = new C3 } val c2 = new C2 } val c1 = new C1 c1.talk("Hello") c1.c2.c3.talk("World") }

129

8. Warum Objekt-Funktionale Programmierung? Die Ausgabe des obigen Beispiels sieht folgendermaen aus:
1 2

C1.talk: Hello C1.talk: C3.talk: World

Der mittels self => denierte Alias fr this in der Klasse C1 kann also in der Klasse C3 als Referenz auf C1 verwendet werden, whrend this innerhalb von C3 auf den Typ von C3 selber verweisen wrde. Wenn die selftype annotation andere Typen enthlt (also nicht leer ist wie self =>), kann sie zustzliche Typerwartungen fr den umgebenden Typ spezizieren. Etwas prosaischer ausgedrckt heit dies, mittels einer selftype annotation lassen sich Abhngigkeiten von anderen Komponenten festlegen. Folgendes Beispiel, das ebenfalls aus Wampler and Payne stammt, zeigt wie die Abhngigkeiten innerhalb einer mehrschichtigen Anwendung mittels selftype annotation ausgedrckt werden knnen [WP09]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

object Comp2 extends Application { trait def } trait def } trait def } trait def } trait def } trait def Persistence { startPersistence: Unit Midtier { startMidtier: Unit UI { startUI: Unit Database extends Persistence { startPersistence = println("Starting Database") ComputeCluster extends Midtier { startMidtier = println("Starting ComputeCluster") WebUI extends UI { startUI = println("Starting WebUI")

} trait App { self: Persistence with Midtier with UI => def run = { startPersistence startMidtier startUI } } object MyApp extends App with Database with ComputeCluster with WebUI MyApp.run }

130

8.3. Komponenten-basierte Programmierung Die Ausgabe des obigen Beispiels sieht folgendermaen aus:
1 2 3

Starting Database Starting ComputeCluster Starting WebUI

Es wurden also die drei abstrakten Traits Persistence, Midtier und UI durch die drei konkreten Traits Database, ComputeCluster und WebUI implementiert. Das Trait App startet dann diese drei konkreten Traits. Ein Anwendungsobjekt (MyApp) kann also von App ableiten und die drei Traits mit with einbinden. Es kann nicht nur - es mu sogar, denn die selftype annotation in App
1

self: Persistence with Midtier with UI =>

erzwingt, da jede von App ableitende Klasse diese drei Traits einbinden mu. Zudem kann App direkt auf die Methoden in diesen drei Traits zugreifen. Wampler and Payne beschreiben diese praktischen Auswirkungen der obigen selftype annotation folgendermassen: The body of the trait can assume it is an instance of Persistence, Midtier, and UI, so it can call methods dened in those types, whether or not they are actually dened at this point. Were doing just that in run. The concrete type that mixes in this trait must also mix in these three other traits or descendants of them. (aus [WP09]) Die obige selftype annotation knnte allerdings durch den in Abschnitt 4.4.5 auf Seite 54 beschriebenen Mixin-Mechanismus mit Traits ersetzt werden (aus [WP09]):
1 2 3

trait App with Persistence with Midtier with UI { def run = { ... } }

Scalas Form der Mehrfachvererbung ist also quivalent zur oben beschriebenen Verwendung von selftype annotations mit dem Zweck der Spezikation von Abhngigkeiten zwischen Klassen und Traits. Warum also die Unterscheidung, und wann wird welche der beiden Formen angewandt? Wampler and Payne erlutern diese Fragestellung folgendermaen: Why, then, are self types useful if they appear to be equivalent to inheritance? There are some theoretical reasons and a few special cases where self-type annotations oer unique benets. In practice, you could use inheritance for almost all cases. By convention, people use inheritance when they want to imply that a type behaves as (inherits from) another type, and they use selftype annotations when they want to express a dependency between a type and other types. (aus [WP09])

131

8. Warum Objekt-Funktionale Programmierung? Eine weitere Anwendung von selftype annotations beschreibt Ghosh [Gho10]. Sie knnen nicht nur erzwingen, welche Traits in eine Klasse (oder ein Trait oder ein Objekt) eingebunden werden mssen, sie knnen auch innerhalb eines Traits verwendet werden, um festzulegen, da dieses nur in eine ganz bestimmte Klasse (oder deren Subklassen) eingebunden werden darf. Das folgende schematische Beispiel (nach Ghosh ) stellt die Klasse Trade und die beiden Traits Tax und Comission dar [Gho10]:
1 2 3 4 5 6 7 8 9 10 11 12 13

class Trade(...) { ... } trait Tax { self: Trade => ... } trait Commision { self: Trade => ... } val t = new Trade(...) with Tax with Comission

Tax und Comission haben durch die selftype annotation


1

self: Trade =>

festgelegt, da sie ausschlielich als mixins fr Trade fungieren. Die drei genannten Formen der Abstraktion (abstrakte Typen als Felder, Modulare mixin Komposition, und die in diesem Abschnitt ausfhrlich beschriebenen selftype annotations ) sind skalierbar, also sowohl auf sehr kleine wie auch auf sehr groe Komponenten anwendbar. Die Skalierbarkeit wird dadurch erreicht, da das Ergebnis einer Komposition dieselben fundamentalen Eigenschaften hat wie die Komponenten, aus denen es besteht. Wenn Klassen (und Traits als besondere Klassen) als Komponenten benutzt werden wie in Scala, ist das Ergebnis einer Komposition aus mehreren Klassen also wieder eine Klasse, die sowohl abstrakte Felder als auch eine selftype annotation haben kann und mit anderen Klassen per mixin Komposition verbunden werden kann [Pay08]. Klassen auf jeder Ebene knnen Objekte erzeugen (Laufzeit-Komponenten), die in Scala Werte erster Klasse und damit frei kongurierbar sind.

8.4. Service-Orientierte Programmierung


Der auf dem mixin von Traits, der Klassenabstraktion und den selftype Annotationen beruhende Kompositionsmechanismus von Scala bildet die Basis fr ein serviceorientiertes Software-Komponentenmodell [OZ05]: Software-Komponenten sind Programmeinheiten, die eine klar denierte Menge an Dienstleistungen anbieten.

132

8.5. Parallele Programmierung Typischerweise sind diese Komponenten nicht unabhngig, sondern basieren auf einer Menge bentigter Dienstleistungen von anderen kooperierenden Komponenten. Scalas Service-orientiertes Software-Komponentenmodell beruht auf Klassen als Komponenten [OZ05]. Konkrete Felder einer Klasse reprsentieren die angebotenen Dienstleistungen, abstrakte Felder die bentigten Dienstleistungen. ber die mixins lassen sich grere Komponenten aus kleineren erzeugen. Der Kompositionsmechanismus assoziiert automatisch die bentigten Dienstleistungen mit den angebotenen Dienstleistungen. Dabei gilt, da konkrete Felder einer Klasse immer die abstrakten berschreiben. Wenn beispielsweise die Klasse A eine abstrakte Methode doSomething hat, und die Klasse C eine konkrete Methode doSomething hat, kann die abstrakte Methode in A einfach durch ein mixin von C implementiert werden, also durch eine der beiden folgenden Klassendenitionen:
1 2

class A extends C ... class A extends B with C ... // mit B als eine beliebige dritte Klasse

Durch dieses Prinzip knnen Komponenten rekursiv zusammengesteckt werden ohne da sie explizite Referenzen aufeinander haben. Es funktioniert auch, wenn viele Dienstleistungen bentigt und angeboten werden, denn die abstrakten und konkreten Versionen der Dienstleistungen werden vom Compiler automatisch zusammengefhrt. Die so entstehenden Komponenten sind erweiterbare Entitten, die durch Ableiten und berschreiben verfeinert werden knnen, genau wie Klassen in einer traditionellen Objektorientierten Klassenhierarchie. Sie knnen sogar benutzt werden, um neue Dienstleistungen zu anderen bereits existierenden Komponenten hinzuzufgen oder deren vorhandene Dienstleistungen zu aktualisieren. Alles in allem erlaubt das Service-orientierte SoftwareKomponentenmodell von Scala eine reibungslose inkrementelle Evolution von Softwaresystemen.

8.5. Parallele Programmierung


8.5.1. Nebenlugkeit und Parallelitt
Eine der Strken Scalas liegt im Bereich der parallelen bzw nebenlugen Programmierung, ein Bereich, der durch den Trend zu verteilten Anwendungen immer wichtiger wird. Um Scalas Ansatz zur Vereinfachung der nebenlugen Programmierung erlutern zu knnen, sollen zunchst zwei Begrispaare deniert werden [BBD+ 03]. Parallele Programme bearbeiten parallel mehrere gleichzeitig, aber unabhngig voneinander laufende Prozesse. Nebenluge Programme sind allgemeiner als parallele Programme, denn bei ihnen ist es gleichgltig, ob die gleichzeitig laufenden Prozesse parallel (also gleichzeitig) oder nacheinander verarbeitet werden.

133

8. Warum Objekt-Funktionale Programmierung? Die Theorie der Nebenlugkeit verspricht eine optimale Ausnutzung der Systemressourcen. In der Praxis mssen die unabhngigen Prozesse aber oft auf das Freiwerden derselben Ressourcen warten, wodurch sie indirekt doch wieder voneinander abhngig werden. Der gleichzeitige Zugri auf dieselbe Ressource birgt potentielle Konikte, die nach Mglichkeit durch eine Synchronisation der nebenlugen Prozesse umgangen werden sollten. Dabei tauschen die Prozesse (zB ber Semaphoren) untereinander Nachrichten aus, um eine Ressource fr die Dauer eines Zugries zu sperren und danach wieder freizugeben. Bei der Synchronisation sind sowohl ein zuviel wie auch ein zuwenig schdlich [OSV08]. Zuwenig Synchronisation ermglicht das Auftreten von Race Conditions, zuviel Synchronisation frdert das Auftreten von Deadlocks. Race condition (kritischer Wettlauf) ist eine Situation, in der zwei nebenluge Prozesse gegenseitig ihr Ergebnis beeinussen und daher die (nicht vorhersagbare) Reihenfolge, in der sie vom System abgearbeitet werden, signikanten Einu auf das Endergebnis beider Prozesse hat. Deadlock (Verklemmung) ist eine Situation, in der zwei oder mehrere Prozesse gegenseitig (vergeblich und daher endlos) auf das Freigeben von Ressourcen warten [BBD+ 03].

8.5.2. Probleme und Lsungsanstze


Java untersttzt nebenluge Programmierung mittels threads, die auf gemeinsame Daten (shared data) zugreifen und diese whrend des Zugries sperren knnen. Dieser Ansatz hat verschiedene Nachteile [OSV08]: beim Programmieren mu stndig ber Sperren und deadlocks nachgedacht werden. threads sind nicht deterministisch (sie sind unvorhersehbar in ihrem Verhalten), wodurch Software-Tests unzuverlssig werden. Korrekte nebenluge Programme hngen daher vor allem von der gedanklichen Leistung der Entwickler ab. threads konsumieren erhebliche Mengen an Speicher, wenn keine Vorkehrungen fr ihre Wiederverwendung getroen werden. Tabelle 8.1 auf der nchsten Seite zeigt, da die maximale Anzahl simultaner Threads in einer JVM stark Hardwareabhngig ist und in diesem Beispiel zwischen ca. 2500 und ca. 32000 liegt. Scala versucht diese Probleme mit seiner scala.actors Bibliothek zu lsen. Actors sind ein alternatives Modell fr nebenluge Programmierung, das wesentlich leichtgewichtiger ist als Javas Threads (ein System, das 5000 simultane Threads in einer JVM erlaubt, kann ber 1,200,000 simultane Actor untersttzen [HO07]). Die Beziehung zwischen Javas Threads und Scalas Actors wird aus folgendem Zitat von Ghosh deutlich [Gho08]:
1 2

as a rst class language end of life

134

8.5. Parallele Programmierung Hardware 2010 i5 MBP, 4GB Xeon 5120, 4GB EC2 m1.small, 1.7GB Atom 330, 2GB EC2 c1.medium, 1.7GB OS OSX 10.6 Debian Lenny 64-bit Ubuntu 10.04 32-bit Windows XP SP3 Ubuntu 10.04 32-bit JVM Apple1.6.0_22-b04-307 Sun1.6.0_12-b04 OpenJDKIcedTea61.8.2 Sun1.6.0_27-b07 OpenJDKIcedTea61.8 Count 2540 31618 7452 5165 7029

Tabelle 8.1.: Maximale Anzahl von Threads in einer JVM (aus [Tho10]) [...] Scala actors have been designed as lightweight event objects, which get scheduled and executed on an underlying worker thread pool that gets automatically resized when all threads block on long running operations. (aus [Gho08]) Die Eigenschaften von Actors lassen sich folgendermaen zusammenfassen [OSV08]: Ein Actor ist eine Thread-hnliche Entitt, die eine Mailbox fr eingehende Nachrichten hat. Ein Actor wird folgendermaen deklariert:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

// von der Klasse scala.actors.Actor ableiten object myActor extends Actor { def act() { // act()-Methode implementieren ... } } myActor.start() // den Actor starten //Alternative Methode einen Actor zu erzeugen import scala.actors.Actor._ cal myActor2 = actor { // die Hilfsmethode actor benutzen ... } // der Actor startet im Moment seiner Definition // kein Aufruf von start() notwendig

Die Kommunikation zwischen Actors basiert auf Nachrichtenversand:


1

myActor ! "tue etwas"

135

8. Warum Objekt-Funktionale Programmierung? Nachrichten landen in der Mailbox des Empfnger-Actors. Dieser implementiert eine receive Methode um die Nachrichten zu verarbeiten. Diese verwendet Pattern Matching um dierenziert auf die Nachrichten zu reagieren.
1 2 3 4 5 6 7

while (true) { receive { case 1 => action1 case 2 => action2 ... } }

Wenn ein Actor eine Nachricht versendet, blockiert er nicht, und wenn ein Actor eine Nachricht empfngt, wird er nicht unterbrochen. Actors folgen der share nothing Philosophie. Wenn Actors ausschlielich ber Nachrichten miteinander kommunizieren, kann in jedem Actor programmiert werden, als ob es sich um ein Programm mit einem einzelnen Thread handelt. Die Daten eines Actors werden nur durch den Thread des Actors angesprochen, brauchen also nicht synchronisiert werden. Put another way, actors allow you to write a multi-threaded program as a bunch of single-threaded programs that communicate with each other via asynchronous messaging. This simplication works, however, only as long as messages are the only way you let your actors communicate. (aus [OSV08]) Erzeugen und Wechseln von Threads sind relativ Speicher- und Prozessor-intensive Vorgnge. In Scala gibt es daher ein pattern (Muster) zur Wiederverwendung von einmal verwendeten threads durch andere Actors, das folgendermaen aussieht:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

def act() { react { // react anstelle von receive ermoeglicht thread-reuse case 1 => action1 act() case 2 => action2 act() ... case "EXIT" => action // kein Aufruf der act()-Methode, // der Actor wird beendet. } }

Durch die Funktion loop aus der Scala Actor Bibliothek kann man sich die erneuten Aufrufe der act() Methode ersparen:

136

8.5. Parallele Programmierung


1 2 3 4 5 6 7 8 9 10 11

// dieser Actor wird endlos loopen und // auf Nachrichten reagieren. def act() { loop { react { case 1 => action1 case 2 => action2 ... } } }

137

9. Zwischenlsung oder neues Paradigma?


9.1. Objekt-Funktional vs Funktional
Ist die Objekt-funktionale Programmierung ein eigenstndiges Programmierparadigma, oder eher eine bergangsphase von der Objekt-orientierten Programmierung zur rein funktionalen Programmierung? Diese Frage kann natrlich nur der Lauf der Zeit abschlieend beantworten, dennoch lt sich auch heute schon ber das richtige Mischungsverhltnis bei der Fusion zweier Programmierparadigmen streiten. Daniel Spiewak ist daher in seinem Blog der Frage Is Scala not functional enough? nachgegangen und hat dabei hochkartige Kommentare zu seinem Artikel bekommen, unter anderem von Martin Odersky (dem Schpfer von Scala) und von Rich Hickley (dem Schpfer von Clojure) [Spi08b]. Er resmiert die Kritik von Rich Hickley und anderen an Scala in Bezug auf die Eignung als funktionale Sprache folgendermaen: The core of the argument made by Rich (and others) against Scala as a functional language goes something like this: Mutable variables as rst-class citizens of the language Uncontrolled side-eects (ties in with the rst point) Mutable collections and other imperative libraries exist on equal footing Object-oriented structures (class inheritance, overloading, etc) Verbosity Diese Kritik beinhaltet eine Gegenberstellung wesentlicher Designentscheidungen bei der Entwicklung von Scala und Clojure. Als modernes Lisp mit einem Schwerpunkt auf der nebenlugen Programmierung hat sich Clojure radikaler von Allem abgewandt, was zu schwer beherrschbarer Komplexitt in der nebenlugen Programmierung beitrgt [VS10]. Dazu gehrt die Abkehr von Objekten und Vererbung genauso wie der berwiegende Verzicht auf vernderbare Kollektionen. Vernderbarer Zustand sollte in einem Clojure Programm als explizit deklarierter Sonderfall und nicht als Regelfall behandelt werden. Es gibt dafr einen speziellen, den bekannten Transaktionen bei Datenbanken nachempfundenen Mechanismus, das sogenannte Software Transactional Memory (STM) System. Obwohl STM bereits von Scala adaptiert wurde ([Gro10]) und Scala ber den Actor-Mechanismus fr die nebenluge Programmierung verfgt (siehe Abschnitt 8.5.2

139

9. Zwischenlsung oder neues Paradigma? auf Seite 134), ist Clojure konsequenter in seiner Eindmmung des vernderbaren Zustandes, der sich auch in kleinen Dosen beraus schdlich auf die Skalierbarkeit und Beherrschbarkeit komplexer nebenluger Programme auswirken kann. Aber auch Clojure ist weit davon entfernt, von Puristen und Haskell-Kennern als rein funktionale Sprache angesehen zu werden, wie der folgende Kommentar von James Iry zu dem Artikel zeigt: "Clojure allows just as much side eecting as Scala with mutable references and uncontrolled I/O. The main thing Clojure brings to the table is that all its various mutable reference types have some notion of thread awareness. But the core truth is this: the Clojure language is just as deeply impure as the Scala language. Now, he [Rich Hickley] is right that Clojures library is more functional. Its data structures are mostly immutable while Scala has a fair number of imperative structures. But theres nothing (other than taste) stopping me from writing mutable structures in Clojure. Die beiden beliebten und erfolgreichen neuen JVM-Sprachen Scala und Clojure sind also eher pragmatische Kompromisse, die im Vergleich zu den derzeit dominierenden Sprachen der Softwareindustrie (Java, C# und C++, nach [Dua11]) erhebliche Verbesserungen bringen, aber nicht mit akademischen Sprachen wie Haskell um konzeptionelle Reinheit im Bereich der funktionalen Programmierung konkurrieren. Scala ist dabei im Vergleich zu Clojure in mancher Hinsicht weniger konsequent bei der Ausrichtung auf Prinzipien und Ideale der funktionalen Programmierung, bietet dafr aber umfangreiche Mglichkeiten fr den Entwurf typsicherer Objekt-orientierter Komponentensysteme. Martin Odersky umschreibt die Situation in einem Kommentar zum Artikel folgendermaen: If you try to achieve a fusion of object-oriented and functional, you sure get some objections from both camps, thats inevitable.

9.2. Objekt-Funktional vs FRP


Die Entwicklung von Programmiersprachen erfolgt nicht im luftleeren Raum, sondern wird von den Bedrfnissen und Problemen der Softwareindustrie bestimmt. Die in dieser Arbeit oft zitierten Moseley and Marks haben in ihrem Artikel Out of the Tar Pit Komplexitt als das fundamentale Problem der Softwareindustrie erkannt, und einen radikal vereinfachten Ansatz fr die Softwareentwicklung vorgeschlagen: die Funktional Relationale Programmierung (FRP) [MM06]: "FRP is currently a purely hypothetical approach to system architecture that has not in any way been proven in practice. It is however based rmly on principles from other areas (the relational model, functional and logic programming) which have been widely proven.

140

9.2. Objekt-Funktional vs FRP In FRP all essential state takes the form of relations, and the essential logic is expressed using relational algebra extended with (pure) user-dened functions. The primary, overriding goal behind the FRP architecture [...] is [...] elimination of complexity. (aus [MM06]) Moseley and Marks haben aufbauend auf ihrer Analyse der Ursachen schwer beherrschbarer Komplexitt in Software-Projekten eine Zerlegung und Bewertung von Komplexitt vorgenommen, die in Tabelle 9.1 dargestellt ist. Complexity Essential Logic Essential Complexity Accidental Useful Complexity Accidental Useless Complexity Type State State/Control State/Control Recommendation Separate Separate Separate Avoid

Tabelle 9.1.: Komplexittstypen innerhalb eines Softwaresystem (aus [MM06]). Grundstzlich unterscheiden sie zwischen essentieller und nicht-essentieller Komplexitt: Essential Complexity is inherent in, and the essence of, the problem (as seen by the users). Accidental Complexity is all the rest complexity with which the development team would not have to deal in the ideal world (e.g. complexity arising from performance issues and from suboptimal language and infrastructure). (aus [MM06]) Da unntze Komplexitt zu vermeiden ist, bleiben drei Komponenten einer anhand der Dimensionen Systemkomponente (State, Logic, Control) und Komplexitt (essential, accidental) zweidimensional ausgerichteten Softwarearchitektur, die ihre ArchitekturKomponenten so unabhngig wie mglich voneinander hlt: Essential State This can be seen as the foundation of the system. The specication of the required state is completely self-contained it can make no reference to either of the other parts which must be specied. One implication of this is that changes to the essential state specication itself may require changes in both the other specications, but changes in either of the other specications may never require changes to the specication of essential state. Essential Logic This is in some ways the heart of the system it expresses what is sometimes termed the business logic. This logic expresses in terms of the state what must be true. It does not say anything

141

9. Zwischenlsung oder neues Paradigma? about how, when, or why the state might change dynamically indeed it wouldnt make sense for the logic to be able to change the state in any way. Changes to the essential state specication may require changes to the logic specication, and changes to the logic specication may require changes to the specication for accidental state and control. The logic specication will make no reference to any part of the accidental specication. Changes in the accidental specication can hence never require any change to the essential logic. Accidental State and Control This (by virtue of its accidental nature) is conceptually the least important part of the system. Changes to it can never aect the other specications (because neither of them make any reference to any part of it), but changes to either of the others may require changes here. (aus [MM06]) Funktional Relationale Programmierung (FRP) basiert also, wie der Name schon sagt, im wesentlichen auf Relationen, relationaler Algebra und (reinen) Funktionen. Daraus resultieren laut Moseley and Marks folgende Vorteile [MM06]: Zustand FRP vermeidet berssigen Zustand und ist explizit so entworfen, da das System gar nicht in einen schlechten Zustand (bad state) geraten kann. Aus Sicht der Logik stellt sich der essentielle Zustand als Konstante dar, und der nichtessentielle Zustand braucht bei der Entwicklung der Systemlogik berhaupt nicht bercksichtigt werden. Der funktionale Teil der Systemlogik hat keinen Zugri auf irgendeine Form von Zustand, er ist vollkommen referentiell transparent (siehe Abschnitt 5.2.1 auf Seite 78). Aufgrund der Datenreprsentation als Relationen wird die bei der Modellierung von Datentypen und Datenhierarchien sonst stets vorhandene Subjektivitt vermieden. Integrity constraints ermglichen, den Zustand auf rein deklarative Art und Weise konsistent zu halten. Kontrollu Die relationale Komponente von FRP enthlt keinerlei Kontrollu. Dieser Teil der Logik besteht einfach aus einer ungeordneten Menge von Gleichungen die auf abgeleitete Relationen angewandt werden. Aufgrund der einheitlichen Reprsentation von Daten als Relationen braucht eine FRP-Infrastruktur keine Zeiger und Zugrispfade verwalten, was das Erzeugen verteilter Anwendungen stark vereinfacht. Codeumfang Durch die Vermeidung berssiger Komplexitt und die strikte Separation der Architektur-Komponenten reduziert FRP den Codeumfang und verringert den mglichen Schaden, der durch die Unbersichtlichkeit umfangreicher Quelltexte und die dadurch verwirrten bzw berforderten Programmierer angerichtet werden kann. Daten Abstraktion Das relationale Modell minimiert die Notwendigkeit subjektiver Datenmodellierung, es mu nur die simple Struktur der (achen) Basisrelationen festgelegt werden. Anwendungsspezische Datenstrukturen werden durch abgeleitete

142

9.2. Objekt-Funktional vs FRP Relationen erzeugt, die nicht durch festgelegte Charakteristiken im Voraus spezizierter abstrakter Datentypen eingeschrnkt sind. FRP verfolgt nicht das Geheimnisprinzip, insbesondere bietet es keine Untersttzung fr verschachtelte Relationen.

FRP geht die gleichen Probleme an wie die Objekt-funktionale Programmierung, die sich unter der berschrift schwer kontrollierbare Komplexitt zusammenfassen lassen, lst sich dabei aber viel radikaler vom Status Quo der derzeitigen Softwareentwicklung. Die zustandsbehaftete Objekt-orientierte Programmierung mit ihrem imperativen Fundament wird bei FRP vllig auer acht gelassen. Auch auf die intelligenten Datenabstraktionen der funktionalen Programmierung wird zugunsten einfacher und acher Relationen ohne interne Geheimnisse verzichtet. Die Idee der reinen Funktionen spielt in FRP eine groe Rolle, aber es wird versucht sie rein deklarativ, ohne jeden festgelegten Kontrollu einzusetzen, also mehr im Sinne der (reinen) logischen Programmierung als der funktionalen Programmierung. Auch in einem weiteren Punkt bildet FRP einen scharfen Kontrast zur Philosophie der Objekt-funktionalen Sprache Scala, wie folgendes Zitat von Moseley and Marks zeigt:

We believe that taking these principles and applying them to the top level of a system design eectively using dierent specialised languages for the dierent components can oer more in terms simplicity than can the unstructured adoption of any single general language (be it imperative, logic or functional) (aus [MM06])

In FRP sollen spezialisierte Sprachen von grtmglicher Einfachheit fr die verschiedenen Architektur-Komponenten verwendet werden, denn die Entwickler dieses Ansatzes sind folgender berzeugung:

Power corrupts (aus [MM06])

Scala ist dagegen als Schweizer Messer der Programmierung aus der diametral entgegengesetzten Idee entstanden, dem Programmierer eine mglichst mchtige Sprache anzubieten, deren power zwar unter Umstnden zweckentfremdet und mibraucht werden knnte, die aber eben auch sehr ezient in sehr vielen Bereichen eingesetzt werden kann [OSV08]. Im folgenden Abschnitt wird beschrieben, wie der Ansatz radikaler Vereinfachung nicht nur auf der Ebene der Architektur, wie bei FRP, sondern auch auf der Ebene der Programmiersprache verfolgt werden kann. Es liegt nahe, da dafr ein Lisp-Dialekt entwickelt wurde, denn aufgrund der reduzierten Syntax und der groen Expressivitt bietet sich Lisp geradezu an, wenn es um minimalistische Einfachheit geht.

143

9. Zwischenlsung oder neues Paradigma?

9.3. Lisp oder nicht Lisp?


9.3.1. Zurck in die Zukunft - der neue Trend zu Lisp
Zwei Entwicklungen haben in letzter Zeit ein verstrktes Interesse an Lisp, dem mittlerweile ber 50 Jahre alten Urahn aller funktionalen Programmiersprachen, aufkommen lassen: Die Mainstream-Sprachen, also die in den Stellenanzeigen dominierenden Programmiersprachen, entwickeln sich seit Jahren (bzw Jahrzehnten) immer mehr in Richtung Lisp [Gra02]. Mit Clojure hat sich ein moderner Lisp-Dialekt eine groe Fangemeinde erobert, indem er neue Lsungen fr neue Probleme anbietet (nebenluge Programmierung fr Multi-Core Maschinen) und die vielgerhmte Expressivitt und Abstraktionsfhigkeit von Lisp mit der Praktikabilitt der JVM verbindet [VS10]. Paul Graham beschreibt die langsame Annherung der Mainstream-Sprachen an Lisp folgendermaen: "Lisp and Fortran were the trunks of two separate evolutionary trees, one rooted in math and one rooted in machine architecture. These two trees have been converging ever since. Lisp started out powerful, and over the next twenty years got fast. So-called mainstream languages started out fast, and over the next forty years gradually got more powerful, until now the most advanced of them are fairly close to Lisp. Close, but they are still missing a few things.... (aus [Gra02])

9.3.2. Die Jahrhundertsprache


In seinem Essay The Hundred-Year Language macht sich Paul Graham Gedanken darber, wie Programmmiersprachen in hundert Jahren aussehen knnten. Dabei sieht er, hnlich wie die Softwarerma Thoughtworks (siehe Abschnitt 8.1 auf Seite 125), wenig Zukunft fr Java als Sprache: I think that, like species, languages will form evolutionary trees, with deadends branching o all over. We can see this happening already. Cobol, for all its sometime popularity, does not seem to have any intellectual descendants. It is an evolutionary dead-end a Neanderthal language. I predict a similar fate for Java. (aus [Gra03]) Als Motivation fr seine grundstzlichen berlegungen gibt er folgendes an: The reason I want to know what languages will be like in a hundred years is so that I know what branch of the tree to bet on now. (aus [Gra03])

144

9.3. Lisp oder nicht Lisp? Obwohl selten explizit gedacht oder ausgesprochen, ist dies wohl eine der Fragen die Programmierer in Zeiten rasanten Wandels in der Welt der Programmmiersprachen am meisten umtreibt, und gleichzeitig die Grundlage der vielen mit quasi-religisem Eifer gefhrten language-wars im Internet darstellt. Da es eine groe Investition bedeutet, sich praxisreife Expertise in einer Programmiersprache zu erarbeiten, bestehen groe Befrchtungen auf das falsche Pferd zu setzen und mit seiner Investition in einer evolutionren Sackgasse zu landen. Diese Frage ist daher auch in Bezug auf die Objekt-funktionale Programmierung in Allgemeinen und Scala im Speziellen von groer Bedeutung. Lohnt es sich auf Scala zu wetten? Werden die Sprachen der Zukunft Weiterentwicklungen der Grundidee von Scala sein, also Schweizer Armeemesser der Programmierung, die unterschiedliche Programmierparadigmen fusionieren? Oder wird sich dieser Weg, trotz anfnglicher und eventuell auch mittelfristiger Erfolge, letztendlich als Sackgasse erweisen und sich, vielleicht, der gegenwrtige Trend in Richtung Lisp weiter fortsetzen? Darauf kann hier natrlich keine Antwort gegeben werden, aber es soll versucht werden, dem Ansatz von Scala einen in mancher Hinsicht radikal anderen (Lisp-basierten) Ansatz gegenberzustellen (siehe Abschnitt 9.3.3 auf Seite 147), und damit vielleicht auch einige selten hinterfragte Grundannahmen kritischer zu beleuchten. In Abschnitt 9.4 auf Seite 151 wird auch noch ein warnendes Beispiel aus der Geschichte der Programmiersprachen aufgezeigt, bei dem der Versuch, eine allumfassende Sprache zu schaen, letztendlich gescheitert ist. Wie stellt sich Paul Graham nun die Hundred-Year Language, die Programmiersprache der Zukunft, das globale Optimum in der Evolution der Programmiersprachen, vor? Zunchst macht er eine wichtige Vorhersage ber die zu erwartende Geschwindigkeit der Computer der Zukunft [Gra03]: Whatever computers are made of in a hundred years, it seems safe to predict they will be much faster than they are now. If Moores Law continues to put out, they will be 74 quintillion (73,786,976,294,838,206,464) times faster. Thats kind of hard to imagine. And indeed, the most likely prediction in the speed department may be that Moores Law will stop working. Anything that is supposed to double every eighteen months seems likely to run up against some kind of fundamental limit eventually. But I have no trouble believing that computers will be very much faster. Even if they only end up being a paltry million times faster, that should change the ground rules for programming languages substantially. Among other things, there will be more room for what would now be considered slow languages, meaning languages that dont yield very ecient code. (aus [Gra03]) Der Geschwindigkeitszuwachs von Computern hat nach Graham entscheidende Konsequenzen fr das Design von Programmiersprachen: I can already tell you whats going to happen to all those extra cycles that faster hardware is going to give us in the next hundred years. Theyre nearly all going to be wasted. [...]

145

9. Zwischenlsung oder neues Paradigma? Theres good waste, and bad waste. Im interested in good waste the kind where, by spending more, we can get simpler designs. How will we take advantage of the opportunities to waste cycles that well get from new, faster hardware? [...] In language design, we should be consciously seeking out situations where we can trade eciency for even the smallest increase in convenience. (aus [Gra03]) Graham unterscheidet zwischen Verschwendung von Programmiererzeit und Verschwendung von Computerzeit. Whrend die Vermeidung des letzteren heutzutage noch wesentlich das Design von Programmiersprachen bestimmt, sollte die Vermeidung des ersteren aufgrund rasend schneller Hardware die Anforderungen an die Programmiersprachen der Zukunft bestimmen. What programmers in a hundred years will be looking for, most of all, is a language where you can throw together an unbelievably inecient version 1 of a program with the least possible eort. At least, thats how wed describe it in present-day terms. What theyll say is that they want a language thats easy to program in. Inecient software isnt gross. Whats gross is a language that makes programmers do needless work. Wasting programmer time is the true ineciency, not wasting machine time. This will become ever more clear as computers get faster. (aus [Gra03]) Nach Graham ist eine Sprache umso besser, je einfacher ihr Sprachkern ist. Er argumentiert, da die meisten Datenstrukturen, die heutzutage wie selbstverstndlich als Teil des Sprachkerns fast aller Programmiersprachen angesehen werden, eigentlich nur der Geschwindigkeit dienen, und somit im Prinzip vorzeitige Optimierungen darstellen, die auf der rasanten Hardware der Zukunft berssig werden: Most data structures exist because of speed. For example, many languages today have both strings and lists. Semantically, strings are more or less a subset of lists in which the elements are characters. So why do you need a separate data type? You dont, really. Strings only exist for eciency. But its lame to clutter up the semantics of the language with hacks to make programs run faster. Having strings in a language seems to be a case of premature optimization. [...] I think getting rid of strings is already something we could bear to think about. [...] How far will this attening of data structures go? [...] Will we get rid of arrays, for example? After all, theyre just a subset of hash tables where the keys are vectors of integers. Will we replace hash tables themselves with lists? [...] Could a programming language go so far as to get rid of numbers as a fundamental data type? [...] I dont see why not. The future is pretty long. If theres something we can do to decrease the number of axioms in the core

146

9.3. Lisp oder nicht Lisp? language, that would seem to be the side to bet on as t approaches innity. If the idea still seems unbearable in a hundred years, maybe it wont in a thousand. (aus [Gra03]) Er schliet mit der Feststellung, da die Sprache der Zukunft im Prinzip schon heute entworfen werden knnte: 1. the hundred-year language could, in principle, be designed today, and 2. such a language, if it existed, might be good to program in today. When you see these ideas laid out like that, its hard not to think, why not try writing the hundred-year language now? (aus [Gra03]) Knnte Scala schon die Sprache der Zukunft sein? Ist das geschickte Verschmelzen der beiden wichtigsten Programmierparadigmen der letzten Jahrzehnte in einer expressiven Sprache mit eher konventioneller Syntax richtungsweisend? Oder gibt Clojure mit seiner sparsamen Lisp-Syntax und der Fokussierung auf funktionale Programmierung unter Vermeidung vernderlichen Zustandes die Richtung vor? Oder ist einer der, aus heutiger Sicht, grten Pluspunkte beider Sprachen, die Verbindung mit Javas exzellenter und schneller Laufzeitumgebung und damit auch einer Unmenge von vorhandenen Java-Bibliotheken, eher ein Hindernis fr die Entwicklung einer Jahrhundertsprache? Paul Graham selber sieht, wie die obigen Zitate zeigen, eher eine konzeptionell minimale Sprache, deren Design von einer nicht mehr einschrnkenden Leistungsfhigkeit der Hardware ausgeht, als mgliches Endprodukt der Evolution der Programmiersprachen an. Er arbeitet bereits an Arc, seiner Version dieser Sprache [Gra03], die sich aber noch in einem relativ frhen Stadium bendet. Es existiert aber schon ein gar nicht so neuer Lisp-Dialekt, der mit seiner faszinierenden Einfachheit und Andersartigkeit dem Idealbild von Grahams Jahrhundertsprache erstaunlich nahe kommt. Abschnitt 9.3.3 stellt eine Programmiersprache vor, die ebenfalls Objekt-funktional ist, aber beim Sprachdesign einen vllig anderen Ansatz als Scala gewhlt hat. Dies soll dazu anregen, auch ganz grundstzliche Eigenschaften von Scala und anderen vielversprechenden neuen Sprachen zu hinterfragen, und nicht ihre Gemeinsamkeiten als selbstverstndlich anzunehmen und den Blick nur auf ihre Unterschiede zu richten.

9.3.3. Skalpell vs Schweizer Armeemesser


PicoLisp ist, aus Anwendersicht, eine Programmiersprache, eine Datenbank, und ein Applikationsserver [Bur11]. Aus Sicht des Sprachdesigners ist PicoLisp zunchst einmal die Architektur einer virtuellen Maschine (VM), und dann eine Programmiersprache [Bur10b]. Der Schpfer von PicoLisp, Alexander Burger, nennt die Sprache das Skalpell der Programmierung, und nimmt implizit Bezug auf Scala, indem er diese Bezeichnung zu Scalas Charakterisierung als Schweizer Messer der Programmierung in Kontrast setzt [Bur11].

147

9. Zwischenlsung oder neues Paradigma? PicoLisp ist nicht neu. Es entstand bereits Anfang der 80er Jahre als Ergebnis von Studien zum Design von Programmiersprachen, und wird seit 1988 in kommerziellen und wissenschaftlichen Programmierprojekten eingesetzt [Bur10b]. Beim Entwurf von PicoLisp ging es um die Frage: Was ist die minimale Architektur fr eine VM, die noch praktisch einsetzbar ist? Mit dieser Ausrichtung und seiner Eigenschaft als Lisp-Dialekt war PicoLisp (zumindest implizit) von Anfang an ein Versuch, eine Jahrhundertsprache nach den Kriterien von Paul Graham zu entwickeln (siehe Abschnitt 9.3.2 auf Seite 144). Die internen Strukturen von PicoLisp sind daher so einfach, da ein erfahrener Programmierer immer verstehen sollte, was unter der Haube bei der Programmausfhrung passiert. Aufgrund seiner praktischen Ausrichtung war PicoLisp aber nie nur ein akademisches Sprachexperiment, die Notwendigkeit von Geschwindigkeit wurde beim Design stets beachtet, ohne den angestrebten Minimalismus zu kompromittieren [Bur10a]. Daraus resultiert eine nach Burger idR drei mal so hohe Geschwindigkeit im Vergleich zu der populren interpretierten Sprache Python [Bur11]. Die PicoLisp VM setzt um, was Graham von einer Jahrhundertsprache gefordert hat: Verzicht auf alle speziellen Datentypen, die nur der Geschwindigkeit dienen, im Kern der Sprache [Bur10b]. Aufbauend auf der Zelle mit den beiden fr Lisp typischen Maschinenwrtern CAR (= head) und CDR (= tail) (siehe Abschnitt 3.3.3 auf Seite 22) besitzt PicoLisp nur drei grundlegende Datentypen: Nummer Ein vorzeichenbehafteter integraler (ganzzahliger) Wert beliebiger Gre. Fliekommazahlen werden durch Skalierung ganzzahliger Werte simuliert. Symbol Bestehend aus einem Wert, einem (optionalen) Namen und einer beliebigen Anzahl von Attributen. Es gibt vier Typen von Symbolen: NIL, ein spezielles Symbol, das genau einmal im ganzen System existiert, und viele verschiedene Funktionen hat. Internal, normale Symbole, die fr Funktionsdenitionen und Variablennamen benutzt werden. Transient, Symbole mit begrenzter Lebensdauer, die bspw als Strings und als anonyme, dynamisch erzeugte Objekte benutzt werden. External, in einer Datenbank-Datei gespeicherte Symbole, die dynamisch und transparent in den Speicher geladen und und wieder zurck in die Datenbank geschrieben werden. Liste (Cons Pair) Eine Sequenz von einer oder mehreren Zellen, die Nummern, Symbole oder wieder andere Listen enthalten. Listen dienen zur Emulation zusammengesetzter Datentypen wie arrays, trees, stacks oder queues. Aufgrund dieser minimalen Ausstattung an Datentypen, die alle auf der Zelle basieren, knnen die unteren 3 Bit der Bit-Reprsentation von Zeigern als Typinformation ber den atomaren Typ der referenzierten Zelle verwendet werden:
1 2

xxxxxxxxxxxxxxxxxxxxxxxxxxxxx010 # Nummer (x = irrelevant) xxxxxxxxxxxxxxxxxxxxxxxxxxxxx100 # Symbol

148

9.3. Lisp oder nicht Lisp? Durch diese mit jedem Zeiger assoziierte Typinformation hat der PicoLisp Interpreter komplettes Wissen ber alle Daten im System, was die Entwicklung eines ezienten, aber einfachen garbage collector vereinfacht. PicoLisp besitzt die mit allen klassischen Lisp-Systemen verbundenen Vorteile: Dynamische Datentypen und -strukturen. Formale Gleichheit von Code und Daten. Eignung fr einen funktionalen Programmierstil. Eine interaktive Laufzeitumgebung. Gleichzeitig wendet sich PicoLisp aber radikal von einigen (auch) unter LispProgrammierern allgemein akzeptierten Glaubensstzen und Dogmen ab [Bur06]: Lisp braucht einen Compiler PicoLisp ist interpretiert, wobei Interpretation aufgrund der extrem einfachen inneren Struktur der Sprache das Evaluieren von Ausdrcken durch das schnelle Verfolgen von Zeigerstrukturen bedeutet. Dies ist viel schneller als die Interpretation von syntaxbehafteten textuellem Programmcode in Sprachen wie zB Python. Lisp braucht viele Datentypen Diese dienen vor allem dazu, die Mglichkeiten eines Compilers bezglich Geschwindigkeiterhhung zu nutzen. PicoLisp ist extrem einfach strukturiert und daher auch ohne Compiler sehr schnell. Dadurch fllt die Notwendigkeit fr all diese speziellen Datentypen weg, da sie mit Symbolen und Listen emuliert werden knnen. Dynamisches Binden ist schlecht PicoLisp implementiert dynamisches aches Binden (dynamic shallow binding). Der gegenwrtige Wert eines Symbols ergibt sich dynamisch aus der Geschichte und dem Zustand der Programmausfhrung und nicht durch statische Inspektion der lexikalischen (textuellen) Umgebung des Symbols. Nicht ungefhrlich, aber durch Namenskonventionen fr Symbole zu beherrschen. Attributlisten (Property Lists) sind schlecht Trotz der generellen Bevorzugung von hash tables und zB binary trees unter Programmierern haben sich die oft als veraltet und primitiv angesehenen property lists in PicoLisp als beste Lsung erwiesen. Eine Denition von Attributlisten ndet sich im GNU Emacs Lisp Manual [Sta10]: A property list (plist for short) is a list of paired elements. Each of the pairs associates a property name (usually a symbol) with a property or value. (aus [Sta10]) PicoLisp ist auch eine Objekt-orientierte Datenbank (realisiert ber externe Symbole) mit einer Prolog-Implementierung in PicoLisp (Pilog ) als Abfragesprache, und ein Applikationsserver mit einer Lisp-basierten Auszeichnungssprache (markup-language) fr HTML-basierte Webclients.

149

9. Zwischenlsung oder neues Paradigma? Bei der Entwicklung von Datenbank-basierter Unternehmenssoftware mit Webclient bricht PicoLisp mit beinahe allen gngigen Konventionen des Software-Engineering, indem es das Lokalittsprinzip verfolgt. Anstatt Daten und Geschftslogik fein suberlich von der Anwendungslogik und der GUI zu trennen, wird alle relevante Information bezglich eines Moduls an einer Stelle zusammengefat. Zudem werden GUI- und DatenbankOperationen direkt miteinander verbunden, dh die HTML-Formulare in der GUI agieren direkt auf den Datenbank-Objekten, die ja ganz normale (externe) Symbole der Sprache sind. Diese Eigenheiten des PicoLisp-Systems ermglichen, ganz im Sinne von Paul Grahams Jahrhundertsprache, die rasante und hchst dynamische Entwicklung komplexer Unternehmensanwendungen, deren Quellcode so kompakt ist, da die Zusammenfassung aller Informationen fr die einzelnen Module an einem Ort nicht zur Unbersichtlichkeit fhrt. Laut Alexander Burger war dies nur mit einem Lisp-Dialekt zu erreichen, denn nur Lisp erlaubt die einheitliche Behandlung von Code und Daten, auf der die Anwendungsprogrammierung in PicoLisp aufbaut [Bur06]. In PicoLisp-Anwendungen werden Funktionsliterale (siehe Abschnitt 6.1.1 auf Seite 95) und ausfhrbare Ausdrcke frei mit statischen Daten vermischt und im Programm herumgereicht. Das dynamische ache Binden (dynamic shallow binding), ohne den berbau lexikalischer Umgebungen, und die direkte Manipulation von Datenstrukturen, ohne diese vorher deklarieren oder initialisieren zu mssen, tragen zur Kompaktheit und Expressivitt von PicoLisp bei. Da es in PicoLisp keine formale Unterscheidung zwischen Datenbank-Objekten und internen Symbolen gibt, gelten all diese Vorteile auch fr die Datenbank-Programmierung, und ermglichen die direkte Verknpfung von GUI- und Datenbank-Operationen im gleichen lokalen Scope, und mit der gleichen Syntax. Burger vermutet, da die Gemeinschaft der Lisp-Programmierer durch jahrzehntelange (in den Anfngen durchaus berechtigte) Vorhaltungen paranoid bezglich der mangelhaften Ezienz von Lisp geworden ist [Bur06]. Diese Kritik hat auf der Hardware von heute viel an Aktualitt eingebt. Fr viele Anwendungen reicht die Ausfhrungsgeschwindigkeit eines interpretierten Lisps wie PicoLisp vollkommen aus, und wenn tatschlich Probleme auftreten, kann die als Performance-Flaschenhals identizierte Stelle des Programmcodes in C implementiert werden. Folgendes Zitat von Graham unterstreicht die Bedeutung eines sehr guten Prolers1 fr die Erzeugung schnellen Codes [Gra01]: A good language, as everyone knows, should generate fast code. But in practice I dont think fast code comes primarily from things you do in the design of the language. As Knuth pointed out long ago, speed only matters in certain
1

Wikipedia deniert proling folgendermaen: In software engineering, proling (program proling, software proling) is a program optimization task that runs a performance analysis tool called a proler with an application under study. [...] Proling is a form of dynamic program analysis that measures, for example, the usage of memory, the usage of particular instructions, or frequency and duration of function calls. (Zitat aus [Wik09])

150

9.4. PL/I, die erfolglose Jahrhundertsprache critical bottlenecks. And as many programmers have observed since, one is very often mistaken about where these bottlenecks are. So, in practice, the way to get fast code is to have a very good proler, rather than by, say, making the language strongly typed. You dont need to know the type of every argument in every call in the program. You do need to be able to declare the types of arguments in the bottlenecks. And even more, you need to be able to nd out where the bottlenecks are. (aus [Gra01]) Die PicoLisp-VM ist nicht Plattform-unabhngig, sie luft auf GNU/Linux-Systemen (und mit Hilfe der Linux-Emulation cygwin auch unter Windows). Anstatt auf der JVM aufzusetzten, wie Scala und Clojure, ist PicoLisp eng mit einem konkreten Betriebssystem verzahnt. Angesichts der Tatsache, da heutzutage ohnehin die meisten Client-Server Anwendungen Web-basiert sind und die allgegenwrtigen Web-Browser als Client benutzen, mu dies keine Einschrnkung sein. Der ursprngliche Grund auf Plattform-unabhngige VM zu setzen (Portabilitt) ist weitestgehend obsolet geworden, wenn trotz der Festlegung auf nur eine Serverplattform jedes beliebige Betriebssystem den Client untersttzt. Die Verzahnung mit Linux ermglicht nicht nur den direkten Aufruf aller C-Bibliotheken des Betriebssystems aus PicoLisp heraus, sie stellt dem gewhnlichen Anwendungsprogrammierer, der sich nie mit den Interna der JVM beschftigen wrde, eine vertraute Plattform zur Verfgung um seine Anwendung gewinnbringend mit den unzhligen anderen GNU/Linux Programmen zu verknpfen. Zusammenfassend kann man PicoLisp als Revoluzzer in der Welt der Programmiersprachen bezeichnen, whrend Scala eher als Reformer erscheint. Falls Paul Graham mit seiner Beschreibung der Jahrhundertsprache richtig liegt, drfte sich langfristig der Ansatz von PicoLisp durchsetzen. Wer in der Softwareindustrie mittelfristig einen NichtJava (und Nicht-C#) Job sucht, ist mit Scala (und Clojure) sicher besser bedient. Das folgende Zitat beschreibt den minimalistischen Ansatz von PicoLisp sehr gut: Perfection is attained not when there is nothing left to add but when there is nothing left to take away (Antoine de Saint-Exupry) (aus [Bur10b]) Im Abschnitt 9.4 wird kurz auf das Schicksal einer Sprache eingegangen, die erfolglos versucht hat Perfektion durch allumfassende Vollstndigkeit zu erreichen.

9.4. PL/I, die erfolglose Jahrhundertsprache


Die imperative Sprache PL/I entstand in den 60er Jahren, einer Zeit, in der geradezu eine explosionsartige Vermehrung der Programmiersprachen zu beobachten war [Lou03]. Nach den groen Erfolgen der ersten hheren Sprachen aus den 50er Jahren fhlte sich

151

9. Zwischenlsung oder neues Paradigma? nahezu jeder berufen, eine Programmiersprache fr sein spezielles Interessengebiet zu entwerfen. Die meisten dieser Sprachen sind heute vergessen, so auch PL/I. PL/I war ein Versuch von IBM, die besten Eigenschaften von seinerzeit etablierten Programmiersprachen wie Algol, Cobol und Fortran in einer einzigen universellen Programmiersprache zu vereinen und damit quasi die Entwicklung der Programmiersprachen abzuschlieen. Bestandteil der Entwurfsziele von PL/I waren folgende Prinzipien [Gro90]: Die Sprache soll fr alle Arten der Programmierung ausgestattet sein. Ein Programmierer kann sich auf einen fr ihn relevanten Teilbereich der Sprache beschrnken und mu nicht die gesamte Sprache lernen. PL/I mu als Mierfolg gewertet werden, da es einerseits sehr schwierig war Compiler fr die Sprache zu schreiben, die Sprache andererseits fr Programmierer schwer zu lernen war und sich ihre Anwendung aufgrund der groen Anzahl unvorhersehbarer Interaktionen zwischen den Elementen der Sprache als sehr fehleranfllig herausstellte. Als Lehre aus dem Scheitern von PL/I wurden die beiden genannten Entwurfsprinzipien als von vorneherein zum Scheitern verurteilt eingestuft. Ein Programmierer, der nur einen Teilbereich der Sprache gelernt hat, knnte bspw einen Fehler machen, aber die Fehlermeldung des Compilers nicht verstehen, da sie sich auf ihm unbekannte Sprachelemente bezieht. Noch gravierender wre es, wenn der Fehler unentdeckt bleibt und das Programm sich in fr den Programmierer unerklrlicher Weise verhlt, da der Fehler auerhalb des von ihm gelernten Teilbereiches der Sprache liegt. Obwohl PL/I einige Neuheiten in die Welt der Programmiersprachen eingebracht hat (Programmierer-denierte Typen, eine Art Template-System sowie eine einfache Form der Ausnahmebehandlung) macht eine ganz andere Fragestellung die Sprache heute wieder relevant: hat Scala aus den Fehlern von PL/I gelernt oder wiederholt es diese mit seinem swiss army knife of programming Ansatz nur? Auch Scala vereint sehr viele Konzepte in einer Sprache und erschwert es einem Programmierer so die gesamte Sprache zu lernen. Es gibt in Scala oft viele Wege, die zum Ziel fhren, was den Programmierer vor die Qual der Wahl stellt und die Zusammenarbeit zwischen Programmierern erschweren kann, die unterschiedliche Teilbereiche, aber nicht die ganze Sprache beherrschen. David Pollak, der Autor des populren Scala-Webframeworks Lift, hat in Bezug auf diese Problematik einen interessanten Vorschlag gemacht, wie Programmierer mit der Komplexitt der Sprache Scala umgehen knnen anstatt von ihr berwltigt zu werden [Pol09]. Dieser wird in Abschnitt 9.5 kurz vorgestellt.

9.5. Scala in zwei Modi


Jede Programmiersprache protiert von einer populren und beliebten Anwendung, die in dieser Sprache geschrieben wurde, und heutzutage handelt es sich dabei oft um ein WebFramework. Das Web-Framework Lift hat zur Bekanntheit und Beliebtheit von Scala beigetragen, indem es viel verspricht und einiges hlt (aus [Pol10]):

152

9.6. Fazit "Lift is the most powerful, most secure web framework available today. [...] Lift applications are: Secure Lift apps are resistant to common vulnerabilities including many of the OWASP Top 10 Developer centeric Lift apps are fast to build, concise and easy to maintain Scalable Lift apps are high performance and scale in the real world to handle insane trac levels Interactive like a desktop app Lifts Comet support is unparalled and Lifts ajax support is super-easy and very secure Der Autor von Lift hat nun in seinem Scala-Buch, aufbauend auf seiner intensiven Beschftigung mit der Sprache whrend der Entwicklung des Frameworks, beschrieben, wie er bei seiner tglichen Arbeit mit Scala bewut zwischen zwei Programmiermodi wechselt [Pol09]: Bibliotheks-Autor Ein Modus, in dem er viel nachdenkt, vielleicht nur wenige Zeilen Code am Tag produziert, und die Mglichkeiten von Scalas Typsystem und Kompositionsmechanismus voll ausschpft, um abstrakten und allgemeinen Code zu produzieren, der von vielen anderen Programmierern in ihren konkreten Anwendungen genutzt werden kann. Bibliotheks-Nutzer Ein Modus, in dem er hunderte von Zeilen Code am Tag fr eine konkrete Anwendung produziert und Scala einfach als expressive und zugngliche Programmiersprache benutzt, ohne viel ber die Feinheiten des Typsystems und die eher esoterischen Mglichkeiten der Sprache nachzusinnen. Fr den Anwendungsprogrammierer ist sicher der Modus des Bibliotheks-Nutzers der default-modus, und es kann hilfreich sein sich bewut zu machen, da man Scala in diesem Modus auch benutzen kann ohne alle Aspekte der Sprache vollkommen durchdrungen zu haben. Andererseits ist es ein groer Vorteil, die von der Anwendungsprogrammierung geluge Sprache auch beim Design und der Implementierung komplexer und abstrakter Software-Bibliotheken verwenden zu knnen.

9.6. Fazit
In dieser Arbeit wurde untersucht, wie und warum die Objekt-funktionale Programmierung aus ihren beiden konstituierenden Bestandteilen, der Objekt-orientierten und der funktionalen Programmierung, hervorgegangen ist. Dabei wurde darauf eingegangen, welche neuen Mglichkeiten eine Objekt-funktionale Sprache wie Scala bietet, und diese dann mit (teilweise radikal) anderen Anstzen verglichen. Es wurden insbesondere zwei Fragen untersucht:

153

9. Zwischenlsung oder neues Paradigma? 1. Kann eine Sprache wie Scala die zentralen Probleme der Softwareentwicklung lsen bzw abmildern (Stichwort Verringerung der Komplexitt )? 2. Wie zukunftsweisend ist das Objekt-funktionale Paradigma, das sich in Scala manifestiert? Auf sehr allgemeine (und natrlich subjektive) Weise knnen einige Pros und Kontras der Programmiersprache Scala folgendermaen zusammengefat werden: Pro Scala Abgesehen von den oft beschriebenen Vorteilen, eine JVM Sprache mit voller Java-Interoperabilitt zu sein [WP09], scheinen die Einfhrung funktionaler Konzepte in die Welt der Objekt-orientierten Programmierung, die verbesserten Kompositionsmglichkeiten anhand abstrakter Typen, die Skalierbarkeit der Konzepte sowie die Untersttzung der nebenlugen Programmierung Scalas groe Strken zu sein. Ein ganz wesentlicher Pluspunkt sind sicher auch die geringen Einstiegshrden fr rein Objekt-orientierte Java und C# Programmierer aufgrund der vollen Objektorientierung und der eher konventionellen Syntax der Sprache. Kontra Scala Die Beschreibung der Strken Scalas liefert paradoxerweise auch gleich die Stichworte, um einige Kritikpunkte an der Sprache zu formulieren. Wenn die funktionalen Konzepte in Scala der Eindmmung des vernderbaren Zustandes dienen und durch abstraktere Kontrollstrukturen den Codeumfang deutlich reduzieren, stellt sich natrlich die Frage, ob die gleichberechtigte Mglichkeit der Objektorientierten (bzw imperativen) Programmierung mit vernderbarem Zustand nicht eher eine Schwche darstellt? Wenn stets empfohlen wird, in Scala einen funktionalen Programmierstil zu entwickeln [Pol09], knnte man auch gleich eine Sprache whlen, die nur diesen funktionalen Stil zult, und das Eindringen des auch in kleinen Dosen schdlichen vernderlichen Zustandes ber imperative Strukturen in das System gar nicht erst ermglicht. Ein weiterer Kritikpunkt hngt mit Scalas ausgefeiltem Typsystem zusammen. Es ermglicht neue Formen der Komposition von Objekten und des mixin von Funktionalitt in Klassen, die mit Java oder anderen Vorgngersprachen nicht oder nur viel schwerer zu realisieren waren [Sch06]. Es lt sich aber auch nicht leugnen, da Scalas Typsystem selber komplex ist, so wie die ganze Sprache mit ihren vielen tiefen Konzepten als komplex bezeichnet werden mu, und nach dem relativ einfachen Einstieg eben doch eine ziemlich steile Lernkurve fr den Programmierer bereit hlt. Es bleibt die Frage, ob eine Sprache, die selber sehr komplex ist, die Lsung fr das Problem unbeherrschbarer Komplexitt in der Softwareentwicklung sein kann? Hier knnte man beispielsweise auf moderne Autos verweisen, deren Innenleben immer komplizierter wird und fr einzelne Automechaniker oder gar Autofahrer gar nicht mehr zu durchschauen ist, deren Bedienung durch den Autofahrer aber eher einfacher als schwieriger wird. Knnte also eine ausgefeilte Programmiersprache als leicht bedienbare blackbox fungieren, die es gewhnlichen Anwendungsprogrammierern vereinfacht, industriell genormte vorgefertigte Softwarekomponenten zu neuen Anwendungen zu verbin-

154

9.6. Fazit den? Wird eine immer komplexere Programmiersprache mit immer ausgefeilteren Konzepten, die von den Programmierern immer weniger Verstndnis fr das verlangt was unter der Haube passiert, das Programmiermodell der Zukunft sein? Moseley and Marks wrden dieser Idee sicher widersprechen, denn sie schlieen ihren Aufsatz Out of the Tar Pit mit folgender Vorhersage ab: So, what is the way out of the tar pit? What is the silver bullet? . . . it may not be FRP, but we believe there can be no doubt that it is simplicity. (aus [MM06]) Die Beschftigung mit minimalistischen Architekturkonzepten wie Functional Relational Programming (FRP) und mit minimalistischen Programmiersprachen wie PicoLisp net die Augen dafr, da es auch ganz andere Wege aus der Softwarekrise geben knnte. Eine Software-Architektur, die ausufernde Komplexitt gar nicht erst zult, indem sie auf betont einfache und weitestgehend unabhngige Architekturkomponenten setzt, in Kombination mit einer (interpretierten) Programmiersprache, die einen minimalen Sprachkern mit maximaler Expressivitt und Dynamik verbindet, knnte durchaus eine Alternative zu Scalas Ansatz darstellen. Dies entsprche dann eher einem whitebox Programmiermodell, bei dem der Programmierer seine Anwendung bis hinab auf das blanke Metall, also bis auf die Ebene der physischen Speicherzelle, verstehen kann. Alexander Burger drckt dies folgendermaen aus: In an ideal world, software development is only planning - the time spent for actually writing code should converge towards zero. We need a programming language which lets us directly express what we want the program to do, in a pragmatic and exible way. And we believe that everything should be as simple as possible, so that the programmer is able to understand at any time what is going on under the hood. (aus [Bur06]) Wenn noch einmal der bildliche Vergleich mit einem Automobil herangezogen wird, htte man es bei diesem Programmiermodell mit einem Fahrzeug mit einem so einfachen Innenleben zu tun, da ein technisch versierter Autofahrer genau verstehen kann was passiert, wenn er beispielsweise das Gaspedal, die Kupplung oder die Bremse tritt, was ihn wiederum in die Lage versetzt, auftretende Probleme zu diagnostizieren und eventuell selbst zu beheben. Ein solches Auto wre dann konkurrenzfhig, wenn es trotz seiner einfachen Bauweise locker und bei angenehmen Fahreigenschaften die erlaubte Hchstgeschwindigkeit auf allen gngigen Straentypen erreicht. Es mte nicht unbedingt mit der Maximalgeschwindigkeit eines modernen komplexen Autos mithalten knnen. Der Vergleich zwischen Scala und PicoLisp zeigt, da es bei diesen ganz unterschiedlichen Anstzen nicht nur um das Programmierparadigma geht, denn beides sind Objektfunktionale Sprachen. Es geht vielmehr um den Kontrast zwischen Einfachheit als einem alles bestimmenden Prinzip, und Mchtigkeit, also dem Bereitstellen tiefer Konzepte und komplexer Funktionalitt (durch einige besonders kompetente Bibliotheksautoren) in einer Form, die sie auch fr durchschnittliche Anwendungsprogrammierer ohne groes Hintergrundwissen anwendbar macht.

155

9. Zwischenlsung oder neues Paradigma? Falls Paul Graham ([Gra03]) und Moseley and Marks ([MM06]) Recht behalten, sollte die Entwicklung der Programmiersprachen langfristig in Richtung Einfachheit in Verbindung mir einem rein funktionalen sowie logisch deklarativen Programmierstil gehen. Dann wre Scala und die Objekt-funktionale Programmierung tatschlich nur eine bergangslsung. Andererseits gibt es neben der Frage, ob Scala langfristig das Zeug zu einer Jahrhundertsprache im Sinne Grahams ([Gra03]) htte, noch eine weitere interessante Frage: knnte Scala kurz- und mittelfristig zu einer populren Sprache werden? Die Kriterien fr eine populre Sprache hat Paul Graham in seinem Aufsatz Being Popular herausgearbeitet [Gra01]). Er beginnt mit einer fr Sprachdesigner wenig ermutigenden Feststellung: A friend of mine once told an eminent operating systems expert that he wanted to design a really good programming language. The expert told him that it would be a waste of time, that programming languages dont become popular or unpopular based on their merits, and so no matter how good his language was, no one would use it. (aus [Gra01]) Obwohl Graham eine recht ausfhrliche Analyse folgen lt, was er alles von einer populren Programmiersprache erwartet, gibt er die Antwort auf die Frage, was eine Sprache populr macht, gleich am Anfang seines Aufsatzes: I think the answer [...] can be found by looking at hackers, and learning what they want. Programming languages are for hackers, and a programming language is good as a programming language (rather than, say, an exercise in denotational semantics or compiler design) if and only if hackers like it. (aus [Gra01]) Mgen Hacker Scala? Hier eine Auistung der Eigenschaften, die eine populre Sprache laut Graham besitzen sollte [Gra01]: beautiful, clean, and terse interactive toplevel syntax [...] is brief to a fault big abstractions write the rst version of a program very quickly really good proler inner loops blindingly fast lots of good examples to learn from, [...] language is intuitive language has a small core, and powerful, highly orthogonal libraries nothing is deprecated, or retained for compatibility source code of all the libraries is readily available

156

9.6. Fazit easy to talk to the operating system and to applications written in other languages language is built in layers higher-level abstractions are built in a very transparent way out of lowerlevel abstractions, which you can get hold of nothing is hidden from you that doesnt absolutely have to be you can change everything about [the language], including even its syntax anything you write has [...] the same status as what comes predened Diese Aufzhlung liest sich fast wie eine Beschreibung von Scala, auer vielleicht der Punkt write the rst version of a program very quickly. Bei einer statisch typisierten Sprache mit mchtigen, aber eben auch komplexen, Mixin- und Kompositionsmechanismen, ist wohl eher eine Ingenieurs-mige, planende Herangehensweise blich, und die Sprache selber erzwingt unter Umstnden schon bei der ersten Version eines hinreichend komplexen Programmes viele wichtige Entscheidungen bezglich Typen und Strukturen, und damit entsprechend langes Nachdenken. Insgesamt knnte man Scala also durchaus gute Chancen einrumen, eine populre Sprache zu werden. Vielleicht liegt es aber gar nicht in der Hand der Scala-Entwickler selbst, Scala zum Erfolg zu fhren. Scalas Zukunft knnte nmlich vor allem von der Entwicklung Javas abhngen. Nachdem Java in dieser Arbeit ziemlich schlecht weggekommen ist (siehe Abschnitt 8.1 auf Seite 125 und Abschnitt 9.3.2 auf Seite 144), kommt die abschlieende Feststellung, da die grte Gefahr fr Scalas Popularitt und potentielle Rolle als Thronfolger Javas vielleicht von Java selbst ausgeht, sicherlich berraschend. Falls sich Java aber in den nchsten Jahren erfolgreich weiterentwickelt und einige der besonders begehrenswerten Eigenschaften Scalas bernimmt, knnte es durchaus passieren, da eine eher konservative Softwareindustrie mit groen Investitionen in Java gar keine Notwendigkeit fr einen Wechsel der Programmiersprache mehr sieht. Wenn die Lcke zwischen Scala und Java kleiner wird, wenn das, was Scala von einem verbesserten Java noch unterscheidet, als nicht auschlaggebend fr die Praxis angesehen wird, knnte Java mit seiner hervorragenden IDE-Untersttzung und seinen unzhligen Frameworks schnell wieder als die bessere Alternative angesehen werden.

157

Teil VI.

Anhang

A. Scala Code ausfhren


A.1. Der Scala Interpreter
hnlich wie viele dynamische Programmiersprachen besitzt Scala eine interaktive Eingabeumgebung, den Scala Interpreter. Er ist hervorragend geeignet um die Sprache auf spielerische Art und Weise zu erforschen oder einfach mal einzelne Skala-Ausdrcke bzw kleine Programme auszuprobieren. Der Scala Interpreter nimmt Ausdrcke entgegen, wertet diese aus und zeigt das Ergebnis an ([OSV08]. Ergebnisse werden automatisch benannt und knnen in nachfolgenden Ausdrcken weiterverwendet werden. Um einen Ausdruck einzugeben der sich ber mehrere Zeilen erstreckt, kann einfach in der nchsten Zeile weitergetippt werden - der Interpreter wei, da der Code unvollstndig ist und reagiert darauf. Die meisten der nicht als Listing deklarierte Code-Beispiele in dieser Arbeit wurden mit dem Scala Interpreter erzeugt.

A.2. Scala Applikationen


Es gibt zwei Arten eine Scala Anwendung zu erzeugen: Singleton-Objekt mit main-Methode Singleton-Objekt das von Trait Application ableitet Die erste Art eine Scala-Anwendung zu erzeugen hnelt der Vorgehensweise in Java, wobei allerdings die Sprachcharakteristiken von Scala zum Tragen kommen. Ein Scala object mit einer main-Methode wird als Einstiegspunkt in eine Applikation deniert. Diese Methode mu eine geeignete Signatur haben, dh sie mu als einzigen Parameter ein Array[String] entgegennehmen.
1 2 3 4 5 6 7 8 9

scala> object ApplicationEntryPoint { | def main(args: Array[String]) { | println("Start the application with the following arguments:") | for (arg <- args) | println(arg) | } | } defined module ApplicationEntryPoint

Der Code fr das Singleton-Objekt sollte in einer Datei mit dem Namen
1

ApplicationEntryPoint.scala

161

A. Scala Code ausfhren gespeichert und auf der Kommando-Zeile mit dem Befehl
1

scalac ApplicationEntryPoint.scala

compiliert werden. Die Anwendung kann dann auf der Kommandozeile mit dem Befehl
1

scala ApplicationEntryPoint arg1 arg2 arg3 argn

gestartet werden. Listing A.1 und Listing A.2 zeigen beispielhaft eine kleine Scala-Applikation die von einem main-Objekt gesteuert wird.
1 2 3 4 5 6 7 8 9 10 11 12 13

object Main { def main(args: Array[String]): Unit = { var nachricht = "" try { nachricht = Rechner.kalkuliereRechnungsBetrag(args(0).toInt) println(nachricht) } catch { case e: NumberFormatException => println("Die Eingabe muss eine ganze Zahl sein") } } }

Listing A.1: Main-Objekt als Eingangspunkt einer Scala-Applikation


1 2 3 4 5 6 7

object Rechner { def kalkuliereRechnungsBetrag(preis: Int): String = { var text = "" if (preis < 25) { text = "Ihre Bestellung muss mindesten 25 Euro betragen - es fehlen noch " + (25-preis) + " Euro"; return text } if (preis > 300) { text = "Ihre Bestellung betraegt ueber 300 Euro - Sie bekommen Rabatt und zahlen nur " + (preis * .95) + " Euro"; return text } text = "Zahlen Sie bitte " + preis + " Euro" return text } }

8 9 10 11

12 13 14 15 16 17 18

Listing A.2: if-Anweisungen als guard clauses zur Behandlung von Spezialfllen

162

A.2. Scala Applikationen Die alternative Art eine Scala-Anwendung zu erzeugen bentigt keine main-Methode und erspart somit einiges an Tipp-Arbeit. In diesem Fall leitet das Scala object vom Trait Application ab, welches eine main-Methode mit geeigneter Signatur zur Verfgung stellt. Die Arbeitsersparnis hat allerdings ihren Preis: der Anwendung knnen keine Kommandozeilen-Argumente bergeben werden, da das args-Array nicht zur Verfgung steht.
1 2 3 4 5 6

scala> object ApplicationEntryPoint2 extends Application { | println("Not usable with command-line arguments") | println("The args Array is not available") | } defined module ApplicationEntryPoint2

163

B. Ausnahmebehandlung in Scala
Ausnahmebehandlung in Scala funktioniert wie in den meisten gngigen Programmiersprachen: eine Methode terminiert entweder auf dem normalen Weg durch Rckgabe eines Wertes oder sie wirft eine Ausnahme (exception) [OSV08]. Der Aufrufer der Methode kann diese Ausnahme behandeln oder sie in der Aufrufkette weiter nach hinten durchreichen. Die Ausnahme wird so lange durchgereicht bis sie von einer Methode behandelt wird oder der Beginn der Aufrufkette erreicht ist und das Programm abbricht. Ausnahmen werden wie in Java durch das Schlsselwort throw geworfen. In Scala ist throw ein Ausdruck der einen Wert vom Typ nothing zurckgibt. Im folgenden Beispiel htte y also den Typ nothing. Bei x = 2 wrde y der Wert 0 vom Typ Int zugewiesen (ganzzahlige Division), bei x = 2.0 der Wert 0.5 vom Typ Double.
1 2 3 4 5 6 7 8 9 10

scala> val x = 0 x: Int = 0 scala> val y = | if (x != 0) { | 1/x | } else { | throw new RuntimeException("Divison by zero not allowed") | } java.lang.RuntimeException: Divison by zero not allowed

Genau wie in anderen Sprachen werden in Scala Anweisungen, die eine Ausnahme erzeugen knnen, in einen try-Block eingeschlossen und die mglichen Ausnahmen in einem nachfolgenden catch-Block behandelt. Innerhalb der catch-Klausel wird die passende Ausnahme mittels Scalas Pattern Matching ausgewhlt. Wenn die geworfene Ausnahme nicht behandelt wird, also keinem Muster zugeordnet werden kann, terminiert der try-catch-Block und reicht die Ausnahme weiter. Optional kann der catch-Klausel noch eine finally-Klausel folgen (oder sie ersetzen). Die Anweisungen im finally-Block werden auf jeden Fall abgearbeitet, unabhngig davon wie der Ausdruck im try-Block terminiert. Anders als bei Java gibt ein try-catch-Block in Scala in der Regel einen Wert zurck. Dies ist der Ergebniswert des try-Blockes, wenn keine Ausnahme geworfen wird, oder der Wert der catch-Klausel mit dem eine Ausnahme abgefangen wird. Nur wenn eine Ausnahme nicht behandelt sondern weitergereicht wird hat ein try-catch-Block keinen Ergebniswert. Die finally-Klausel ist eher fr Aufrumarbeiten zustndig und sollte den Rckgabewert des try-catch-Blockes nicht ndern. Ein expliziter return-Befehl oder eine Ausnahme in der finally-Klausel ersetzten jedoch den im try-catch-Block generierten Rckgabewert.

165

B. Ausnahmebehandlung in Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14

scala> val x = "String" x: java.lang.String = String scala> try { | x.asInstanceOf[Int] | } catch { | case e: RuntimeException => println("Cannot cast String to Int") | } finally { | println("val x is of type String is " + x.isInstanceOf[String]) | } Cannot cast String to Int val x is of type String is true res2: AnyVal = ()

166

C. Anwendungsgebiet XML-Verarbeitung
C.1. Scalas XML Untersttzung
C.1.1. XML - die Sprache des Web
Die Extensible Markup Language (XML) ist eine Metasprache, mit der die Struktur von Dokumenten beschrieben wird [BBD+ 03]. Sie wurde 1998 vom W3C (World Wide Web Consortium) als neuer Standard vorgestellt. Ein XML-Dokument ist Medienund Plattform-neutral und kann unter allen Betriebssysteppmen verarbeitet werden. Dadurch ist XML zur lingua franca fr den Datenaustausch im Internet geworden. Listing C.1 zeigt die Struktur eines einfachen XML-Dokumentes. Die Inhaltskomponenten des Textes werden durch Markup (oder Tags ) markiert und dann baumartig ineinander verschachtelt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE addresses SYSTEM "addresses.dtd"> <addresses> <person idnum="007"> <lastName>Bond</lastName> <firstName>James</firstName> <phone location="mobile">(101) 333-4567</phone> <email>james@bond.uk</email> <address> <street>101 Small Street</street> <city>London</city> <state>Greater London</state> <zip>W112BQ</zip> </address> </person> </addresses>

Listing C.1: Beispiel fr ein XML-Dokument (nach [App10]) Das Taggen geschieht in der Regel auf der Basis einer Dokumenten Typ Denition DTD oder eines sogenannten Schemas. Dies ist aber optional.

C.1.2. Scalas Datenmodell fr XML


Wenn XML-Dokumente mit Programmiersprachen verarbeitet werden, geschieht dies auf der Basis eines Datenmodells. Je besser die Konzepte der Sprache und das Datenmodell

167

C. Anwendungsgebiet XML-Verarbeitung zusammenpassen desto einfacher und ezienter wird die Dokumentenverarbeitung.

"XML is a form of semi-structered data. It is more structered than plain strings, because it organizes the contents of the data into a tree. It is less structered than the objects of a programming language, though, as it admits free-form text between tags and it lacks a type system. (aus [OSV08])

Das vom W3C selbst entwickelte Document Object Model (DOM) ist charakteristisch fr eine Reihe von Objekt-orientierten XML Datenmodellen [EMO06]. Innerhalb von DOM wird die geordnete Baumstruktur eines XML-Dokumentes doppelt verlinkt: es gibt jeweils zwei gegenluge Zeiger zwischen einem Nachfolger- und einem Vorgngerknoten (zwischen Vtern und Shnen) sowie zwischen benachbarten Knoten der gleichen Ebene (Geschwistern). Abbildung C.1 zeigt (schematisch) das XML-Dokument aus Listing C.1 auf der vorherigen Seite als DOM, wobei auf die Darstellung der meisten Zeiger verzichtet wurde. addresses

firstName

person

lastName

email city

address

phone state

street

zip

Abbildung C.1.: Beispiel fr ein DOM (Document Object Model) (nach [App10]) Obwohl die vielen Zeiger einem Programmierer viel Flexibilitt im Umgang mit einem XML-Dokument geben, ist diese Darstellung doch sehr Speicher-intensiv und wortreich. Zudem knnen unbeabsichtigt Zirkularitten erzeugt werden die die Termination eines Dokument-verarbeitenden Programmes verhindern. Scalaa Datenmodell fr XML-Dokumente ist ein immutable, ordered (singly-linked) unranked tree (ein unvernderlicher, geordneter, einfach-verlinkter Baum ohne Rangordnung). Dies entspricht der funktionalen Sicht auf einen Baum, bei der keine Zeiger zu Vorgnger- oder Nachbarknoten zur Verfgung gestellt werden, und ist ein wesentlich schlankeres Modell als das DOM.

168

C.1. Scalas XML Untersttzung

C.1.3. XML-Dokumente erzeugen


Ein XML-Dokument besteht im Wesentlichen aus zwei grundlegenden Elementen: text und tags, wobei zwischen start-tags und end-tags unterschieden wird. Verschiedene TagPaare knnen ineinander verschachtelt sein, drfen sich aber nicht berlappen. Jedes Paar zusammengehrender Start- und End-Tags formt ein element. Ein XML-Dokument besteht also aus ineinander verschachtelten Elementen. In der Baumdarstellung sind dies die Knoten, deren hierarchische Beziehungen untereinander durch die verbindenden Kanten dargestellt wird. Wichtige Scala-Klassen zur Darstellung und Verarbeitung von XML sind: Elem Ein XML-Element mit Tags und nachfolgenden Elementen. Node Die abstrakte Superklasse aller XML Knoten-Klassen NodeSeq Eine Sequenz von Knoten. Kann auch nur einen Knoten enthalten, der dann als einelementige Sequenz angesehen wird. Text Ein Knoten, der nur Text enthlt. XML kann in Scala als Literal berall dort geschrieben werden, wo auch ein Ausdruck erlaubt ist:
1 2

scala> <tagA>text<tagB/></tagA> res0: scala.xml.Elem = <tagA>text<tagB></tagB></tagA>

Listing C.2 zeigt, wie aus der inneren Datenstruktur einer Scala-Klasse XML gewonnen werden kann. Das Beispiel zeigt, da in einem XML-Literal Scala-Code evaluiert werden kann, wenn dieser in geschweifte Klammern geschrieben wird. Die wundersame Erzeugung einer Instanz aus der abstrakten Klasse Person mittels new erklrt sich mit der Magie anonymer Subklassen in Scala, auf die hier aber nicht nher eingegangen werden soll (mehr dazu bei [OSV08]).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

object tinyXMLapp extends Application { abstract class Person { val name: String val vorname: String val geburtsjahr: Int def toXML = <person> <name>{name}</name> <vorname>{vorname}</vorname> <geburtsjahr>{geburtsjahr}</geburtsjahr> </person> } val scalinventor = new Person { val name = "Odersky"

169

C. Anwendungsgebiet XML-Verarbeitung
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

val vorname = "Martin" val geburtsjahr = 1958 } val actor = new Person { val name = "Sheen" val vorname = "Martin" val geburtsjahr = 1940 } val scalinventorXML = scalinventor.toXML val actorXML = actor.toXML // for query val query = for (val s <- scalinventorXML; val a <- actorXML; s \ "vorname" == a \ "vorname") yield <actor> {a.child} </actor> print("\n" + scalinventorXML + "\n" ) print("\n" + query + "\n") }

Listing C.2: Konversion interner Datenstrukturen einer Scala-Klasse zu XML Die Ausgabe von Listing C.2 auf der vorherigen Seite sieht folgendermaen aus:
1 2 3 4 5 6 7 8 9 10 11

<person> <name>Odersky</name> <vorname>Martin</vorname> <geburtsjahr>1958</geburtsjahr> </person> <actor> <name>Sheen</name> <vorname>Martin</vorname> <geburtsjahr>1940</geburtsjahr> </actor>

Die for Abfrage ber ein XML-Dokument wird in Abschnitt C.1.6 auf Seite 172 erlutert.

C.1.4. XML-Dokumente einlesen


Die folgenden drei Methoden sind von elementarer Bedeutung, wenn es darum geht XMLDokumente programmatisch auseinander zu nehmen: text extrahiert den gesamten Text aus einem XML-Knoten (ohne Tags).

170

C.1. Scalas XML Untersttzung \ "tagname"bzw "@attributename" extrahiert alle Sub-Elemente (oder Attribute) mit dem angegebenem Namen. \\ "tagname" bzw "@attributename" extrahiert alle Sub-Elemente (oder Attribute) mit dem angegebenem Namen per deep search.

Hier ein Beispiel fr die Extraktion des Subelements name und des darin enthaltenen Textes:
1 2 3 4 5 6 7 8 9 10 11 12

scala> val scalinventor = <person> <name>Odersky</name> <vorname>Martin</vorname> <geburtsjahr>1958</geburtsjahr> </person> scala> val name = scalinventor \ "name" name: scala.xml.NodeSeq = <name>Odersky</name> scala> name.text res12: String = Odersky

Die Methode des deep search erlaubt auch die nachfolgenden Elemente des angegebenen Knotens zu extrahieren:
1 2 3 4 5 6 7 8

scala> val nested = <a><b><c>text</c></b></a> nested: scala.xml.Elem = <a><b><c>text</c></b></a> scala> nested \ "a" res15: scala.xml.NodeSeq = scala> nested \\ "a" res16: scala.xml.NodeSeq = <a><b><c>text</c></b></a>

Abschlieend noch ein Beispiel fr die Extraktion der Attribute mode und notes aus dem XML-Element chord:
1 2 3 4 5 6 7 8 9 10

scala> val chord = <chord | | mode="minor" | | notes="3"/> chord: scala.xml.Elem = <chord notes="3" mode="minor"></chord> scala> chord \"@mode" res3: scala.xml.NodeSeq = minor scala> chord \"@notes" res4: scala.xml.NodeSeq = 3

171

C. Anwendungsgebiet XML-Verarbeitung

C.1.5. Pattern Matching auf XML


Scalas Pattern Matching eignet sich hervorragend, um XML-Dokumente auseinander zu nehmen. Ein XML-Pattern sieht dabei genauso aus wie ein XML-Literal. Nur wenn innerhalb eines XML-Patterns ein Paar geschweifter Klammern verwendet wird, handelt es sich beim enthaltenen Scala-Code nicht um einen Ausdruck, sondern um ein Pattern. Es knnen also beispielsweise neue Variablen gebunden oder Typ-Tests durchgefhrt werden [OSV08]. Das folgende Beispiel zeigt, wie Pattern Matching benutzt wird, um ein XML-Element auszuwhlen, und der Inhalt dieses Elementes dann an die Variable contents gebunden wird.
1 2 3 4 5 6 7 8 9 10 11 12 13

scala> val nested = <a><b><c>text</c></b></a> nested: scala.xml.Elem = <a><b><c>text</c></b></a> scala> def proc(node: scala.xml.Node): | | node match { | | case <a>{contents}</a> => | | case <c>{contents}</c> => | | case _ => "it is not a or | | } proc: (node: scala.xml.Node)String String = "a has content: " + contents "c has content: " + contents c"

scala> proc(nested) res17: String = a has content: <b><c>text</c></b>

C.1.6. XML-Queries mit For Comprehensions


Scalas mchtiger for Ausdruck ermglicht ebenfalls Abfragen auf XML-Daten. Er ist in seiner Funktionalitt komplementr zum Pattern Matching auf XML anzusehen. Whrend letzteres hchstens 1 Ergebnis pro Pattern zurck gibt, gibt eine for Abfrage (Query) genau wie eine SQL-Datenbankabfrage alle passenden Ergebnisse im XML-Dokument zurck [EMO06]. Listing C.2 auf Seite 169 zeigt wie ein for Ausdruck mit zwei Generatoren und einem Filter (siehe Abschnitt 6.5 auf Seite 106) auf die erzeugten XML-Dokumente angewendet wird und die Kind-Elemente des passenden XML-Knotens mit einem neuen Paar umschlieender Tags (<actor></actor>) zurckgegeben werden.

C.1.7. XML-basierte Web-Services


Web Services als wichtiges neues Programmierparadigma benutzen XML als Datenformat fr die groen und strukturierten Argumente ihrer Methodenaufrufe und fr deren Rckgabewerte (Listings C.3 auf der nchsten Seite und C.4 auf der nchsten Seite zeigen zwei einfache Beispiele). Durch den Versand von mehr Information in den MethodenArgumenten kann besser auf Fehler bei der Kommunikation ber das Web reagiert werden. Anstatt Zustand auf dem Server zu speichern, transformieren die Web Services ein-

172

C.1. Scalas XML Untersttzung gehende Nachrichten-Dokumente in ausgehende Nachrichten-Dokumente [EMO06]. Dies kann konzeptionell als die Transformation einer Datenstruktur in Baumform angesehen werden, eine Spezialitt der funktionalen Programmiersprachen und eher eine Schwche der konventionellen Sprachen des Objekt-orientierten Mainstreams (wie Java).
1 2 3 4 5 6 7 8 9 10 11

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/"> <soapenv:Header/> <soapenv:Body> <tem:AddThis> <tem:x>5</tem:x> <tem:y>10</tem:y> </tem:AddThis> </soapenv:Body> </soapenv:Envelope>

Listing C.3: Beispiel fr einen Web-Service Request (nach [Col11])


<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <soap:Body> <AddThisResponse xmlns="http://tempuri.org/"> <AddThisResult>15</AddThisResult> </AddThisResponse> </soap:Body> </soap:Envelope>

1 2 3 4 5 6 7 8 9 10

Listing C.4: Beispiel fr einen Web-Service Response (nach [Col11]) Die Schwchen der Mainstream-Sprachen wie Java beim Umgang mit Baumstrukturen oenbaren sich auch durch die Tatsache, da viele Web Services derzeit mit mehreren Sprachen konstruiert werden, zB Java fr die Businesslogik, XQuery fr den Datenbankzugri und XSLT fr die Dokumenten-Transformation. Die verschiedenen Sprachen mssen dabei zur Zusammenarbeit berredet werden, und genau dort, wo man sie am dringendsten braucht, fehlt dann die statische Typsicherheit - an den Schnittstellen zwischen den Sprachen. Ein weiteres Problem im Bereich der Web Services ist der fr die imperative und Objekt-orientierte Programmierung so charakteristische vernderbare Zustand. Es ist viel schwieriger, Komponenten mit vernderbarem Zustand zu replizieren oder nach einem Fehler wiederherzustellen als Komponenten mit unvernderbarem Zustand. Da Scala die Konzepte funktionaler und Objekt-orientierter Programmierung in sich vereint, lassen sich mit der Sprache auf eziente Weise und unter voller Ausnutzung der statischen Typsicherheit Web Services aus einem Gu erzeugen. Case Classes, Pattern Matching ber Klassenhierarchien und Pattern Matching ber regulre Sequenzen sind

173

C. Anwendungsgebiet XML-Verarbeitung funktionale Spracheigenschaften von Scala die die Verarbeitung (semi-) strukturierter Daten sehr erleichtern.

174

Literaturverzeichnis
[App10] (Inc.) Apple. Constucting XML Tree Structures. https://developer.apple. com/library/mac/#documentation/Cocoa/Conceptual/XMLParsing/ Articles/ConstructingTrees.html, 2010. [Online; accessed 12-December2012]. J. Backus. Can programming be liberated from the von Neumann style?: a functional style and its algebra of programs. In ACM Turing award lectures, page 1977. ACM, 2007.

[Bac07]

[BBD+ 03] S. Barnert, M. Boeckh, D.M. Delbrck, W. Greulich, C. Heinisch, R. Karcher, K. Lienhart, D.G. Radons, S. Voets, and K. Wallenwein. Fachlexikon Computer-Das umfassende Anwenderlexikon fr den gesamten IT-Bereich und alle Plattformen. Brockhaus, 2003. [BD06] [Boe06] [Bur06] B. Brgge and A.H. Dutoit. Objektorientierte Softwaretechnik mit UML, Entwurfsmustern und Java. Pearson Studium, 2006. Sebastian Boelter. Seminar Typsysteme-Scala. http://uebb.cs.tu-berlin. de/lehre/2005WStypsysteme/scala.pdf, 2006. Alexander Burger. Pico Lisp - A Radical Approach to Application Development. http://software-lab.de/radical.pdf, 2006. [Online; accessed 03-June-2011]. Alexander Burger. Need for Speed. http://picolisp.com/5000/-2-F.html, 2010. [Online; accessed 03-June-2011]. Alexander Burger. The PicoLisp Reference. http://software-lab.de/doc/ ref.html, 2010. [Online; accessed 03-June-2011]. Alexander Burger. Picolisp: The Scalpel of Programming. http://picolisp. com/5000/-2.html, 2011. [Online; accessed 03-June-2011]. Aemon Cannon. Building an IDE with the Scala Presentation Compiler: A Field Report. http://ensime.blogspot.com/, 2010. [Online; accessed 30July-2011]. Aemon Cannon. ENSIME - ENhanced Scala Interaction Mode for Emacs. http://aemon.com/file_dump/ensime_manual.html, 2011. [Online; accessed 30-July-2011].

[Bur10a] [Bur10b] [Bur11] [Can10]

[Can11]

175

Literaturverzeichnis [CGLO06] V. Cremet, F. Garillot, S. Lenglet, and M. Odersky. A core calculus for Scala type checking. Mathematical Foundations of Computer Science 2006, pages 123, 2006. [Col11] Joe Colantonio. How to Test a Web Service - Response and Requests (Part 3). http://www.joecolantonio.com/2011/04/21/part-3-%E2%80% 93-how-to-test-a-web-service-%E2%80%93-response-and-requests/, 2011. [Online; accessed 12-December-2012]. W.R. Cook. Object-oriented programming versus abstract data types. In REX School/Workshop Proceedings, 1990. P. Deitel and H. Deitel. Java how to program. Prentice Hall Press Upper Saddle River, NJ, USA, 2006. L. DeMichiel and R. Gabriel. The common lisp object system: An overview. In ECOOP87 European Conference on Object-Oriented Programming, pages 151170. Springer, 1987. Gustavo Duarte. Programming Language Jobs and Trends. http://duartes. org/gustavo/blog/post/programming-language-jobs-and-trends, 2011. [Online; accessed 25-July-2011].

[Coo90] [DD06] [DG87]

[Dua11]

[EMO06] B. Emir, S. Maneth, and M. Odersky. Scalable programming abstractions for XML services. Dependable Systems: Software, Computing, Networks, pages 103126, 2006. [Fol07] Bob Foltrigg. Scala: Tail-Recursive Factorial. http://diamondinheritance. blogspot.com/2007/12/scala-tail-recursive-factorial.html, 2007. [Online; accessed 08-July-2011]. R.H. Gting and S. Dieker. Datenstrukturen und Algorithmen. Vieweg+ Teubner Verlag, 2004.

[GD04]

[GHJV95] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design patterns: elements of reusable object-oriented software, volume 206. Addison-wesley Reading, MA, 1995. [Gho08] Debasish Ghosh. Scala Actors 101 - Threadless and Scalable. http://java. dzone.com/articles/scala-threadless-concurrent, 2008. [Online; accessed 07-August-2011]. Debasish Ghosh. Scala Self-Type Annotations for Constrained Orthogonality. http://debasishg.blogspot.com/2010/02/scala-self-type-\ annotations-for.html, 2010. Online; accessed 14-August-2011. Paul Graham. Being Popular. http://www.paulgraham.com/popular.html, 2001. [Online; accessed 21-July-2011].

[Gho10]

[Gra01]

176

Literaturverzeichnis [Gra02] [Gra03] [Gro90] [Gro09] Paul Graham. Revenge of the Nerds. http://www.paulgraham.com/icad. html, 2002. [Online; accessed 02-June-2011]. Paul Graham. The Hundred-Year Language. http://www.paulgraham.com/ hundred.html, 2003. [Online; accessed 02-June-2011]. Peter Grogono. The Evolution of Programming Languages. University of Jyvskyl Summer School, August 1990. Daniel Gronau. Closures und erstklassige Funktionen. http://dgronau. wordpress.com/tag/funktion-hoherer-ordnung/, 2009. [Online; accessed 08-July-2011]. The Scala STM Expert Group. STM comes to Scala. http://www. scala-lang.org/node/8359, 2010. [Online; accessed 02-June-2011].

[Gro10]

[GWB91] R.P. Gabriel, J.L. White, and D.G. Bobrow. CLOS: Integrating objectoriented and functional programming. Communications of the ACM, 1991. [HO07] [Hoa09] [Hua08] P. Haller and M. Odersky. Actors that unify threads and events. In Coordination Models and Languages, pages 171190. Springer, 2007. CAR Hoare. Null references: The billion dollar mistake. Presentation at QCon London, 2009. Min Huang. Scala: so many ways to say nothing. http://www. lousycoder.com/blog/index.php?/archives/92-Scala-So-many-ways-\ to-say-nothing.html, 2008. [Online; accessed 08-July-2011]. James Iry. Monads are elephants. http://james-iry.blogspot.com/2007/ 10/monads-are-elephants-part-3.html, 2007. [Online; accessed 19-July2011]. U. Ligges. Programmieren mit R. Springer, 2008. K.C. Louden. Programming Languages: Principles and Practice. Brooks/Cole, 2003. and PWS Publishing, 2003. Jim McBeath. Scala Class Linearization. http://jim-mcbeath.blogspot. com/2009/08/scala-class-linearization.html, 2009. [Online; accessed 30-July-2011]. R. Milner. The denition of standard ML: revised. The MIT Press, 1997. B. Moseley and P. Marks. Out of the tar pit. Citeseer, 2006.

[Iry07]

[Lig08] [Lou03] [McB09]

[Mil97] [MM06]

[OAC+ 04] M. Odersky, P. Altherr, V. Cremet, B. Emir, S. Maneth, S. Micheloud, N. Mihaylov, M. Schinz, E. Stenman, and M. Zenger. An overview of the Scala programming language. LAMP-EPFL, 2004.

177

Literaturverzeichnis [OCRZ03] M. Odersky, V. Cremet, C. Rckl, and M. Zenger. A nominal theory of objects with dependent types. ECOOP 2003Object-Oriented Programming, pages 201224, 2003. [Ode06] Martin Odersky. The Scala experiment: can we provide better language support for component systems? In POPL 06: Conference record of the 33rd ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 166167, New York, NY, USA, 2006. ACM. M. Odersky. Scala by example. DRAFT http: // www. scala-lang. org/ docu/ files/ ScalaByExample. pdf , 2009. M. Odersky. Scala Language Reference. DRAFT http: // www. scala-lang. org/ docu/ files/ ScalaByExample. pdf , 2009. Martin Odersky, Lex Spoon, and Bill Venners. Programming in Scala: A Comprehensive Step-by-step Guide. Artima Incorporation, USA, 2008. M. Odersky and M. Zenger. Scalable component abstractions. In Proceedings of the 20th annual ACM SIGPLAN conference on Object-oriented programming, systems, languages, and applications, page 57. ACM, 2005. Alex Payne. Scala lift-o: Martin Odersky Keynote. http://al3x.net/2008/ 05/10/scala-lift-off-martin-odersky-keynote.html, 2008. [Online; accessed 31-June-2011]. Peter Pepper. Funktionale Programmierung. Springer, 2003. S.L. Peyton Jones and P. Wadler. Imperative functional programming. In Proceedings of the 20th ACM SIGPLAN-SIGACT symposium on Principles of programming languages, pages 7184. ACM, 1993. David Pollak. Scala the statically typed dynamic language. http://www. scala-blogs.org/2007/12/scala-statically-typed-dynamic-language. html, 2007. [Online; accessed 18-July-2011]. David Pollak. Beginning Scala. Springer, 2009. David Pollak. What is Lift? http://liftweb.net/, 2010. [Online; accessed 02-June-2011]. Bernie Pope. Monads in scala. http://www.berniepope.id.au/docs/scala_ monads.pdf, 2010. [Online; accessed 19-July-2011]. K. Sierra and B. Bates. SCJP Sun Certied Programmer for Java 6. McGrawHill, 2008. U. Scheben. Simplifying and Unifying Composition for Industrial Component Models. FernUniversitt In Hagen, 2006.

[Ode09a] [Ode09b] [OSV08] [OZ05]

[Pay08]

[Pep03] [PJW93]

[Pol07]

[Pol09] [Pol10] [Pop10] [SB08] [Sch06]

178

Literaturverzeichnis [Seb96] [SK09] [SKS09] [Spi08a] R.W. Sebesta. Concepts of programming languages. Addison-Wesley Longman Publishing Co., Inc. Boston, MA, USA, 1996. F. Steimann and D. Keller. Moderne Programmiertechniken und -methoden (Kurs01853). FernUniversitt in Hagen, 2009. F. Steimann, D. Keller, and D. Stadtler. Objektorientierte Programmierung (Kurs 01814). FernUniversitt in Hagen, 2009. Daniel Spiewak. Function currying in scala. http://www.codecommit.com/ blog/scala/function-currying-in-scala, 2008. [Online; accessed 19-July2011]. Daniel Spiewak. Is Scala Not Functional Enough. http://www.codecommit. com/blog/scala/is-scala-not-functional-enough, 2008. [Online; accessed 02-June-2011]. R.M. Stallman. GNU Emacs Lisp Reference Manual. Free Software Foundation, Boston, 2010. Monika Sturm. Funktionale Programmierung und Typtheorie. http://lat. inf.tu-dresden.de/teaching/ws2007-2008/FunkProg/, 2007. [Online; accessed 22-June-2011]. F. Syska and C. Wilhelmi. Scala - Java-Nachfolger oder akademisches Experiment? Kontrollabstraktion (neue Kontrollstrukturen). https: //sol.cs.hm.edu/rs/scala/06-kontrollabstraktion-syska-wilhelmi/ praesentation.pdf, 2010. [Online; accessed 04-August-2011]. Stu Thompson. Maximum numbers of threads in a JVM. http:// geekomatic.ch/2010/11/24/1290630420000.html, 2010. [Online; accessed 01-July-2011]. Thoughtworks. Technology Radar January 2011. http://www. thoughtworks.com/articles/technology-radar-january-2011#toc2, 2011. [Online; accessed 30-June-2011]. Tierschutzorg. Systematik. http://www.tierschutz.org/tierwelt/ systematik.php, 2010. [Online; accessed 28-July-2011].

[Spi08b]

[Sta10] [Stu07]

[SW10]

[Tho10]

[Tho11]

[Tie10]

[TJJ+ 04] E. Truyen, W. Joosen, B.N. Jrgensen, P. Verbaeten, and KU Leuven. A generalization and solution to the common ancestor dilemma problem in delegation-based object systems. In Dynamic Aspects Workshop (DAW04), page 6. Citeseer, 2004. [Tol09] Andreas Toll. Leibniz Philosophie der Monaden. http://www.suite101. de/content/leibnitz-philosophie-der-monaden-a58452, 2009. [Online; accessed 04-August-2011].

179

Literaturverzeichnis [VS09] Bill Venners and Frank Sommers. A conversation with martin odersky. http: //www.artima.com/scalazine/articles/scalas_type_system.html, 2009. [Online; accessed 19-July-2011]. L. VanderHart and S. Sierra. Practical Clojure. Springer, 2010. D.A. Watt. Programmiersprachen: Konzepte und Paradigmen. C. Hanser, 1996. Wikipedia. Proler. http://en.wikipedia.org/wiki/Profiler_ (computer_science), 2009. [Online; accessed 07-August-2011]. Wikipedia. Assignment. http://en.wikipedia.org/wiki/Assignment_ (computer_science), 2011. [Online; accessed 08-June-2011]. D. Wampler and A. Payne. Programming Scala. O. Reilly, 2009.

[VS10] [Wat96] [Wik09] [Wik11] [WP09]

180

Index
Abfragesprache, 149 Abhngigkeiten, 67, 68, 130, 131 Ableiten, 33, 51, 60, 129, 131, 133 Abstraktion, 2, 19, 21, 29, 49, 64, 91, 116, 117, 128, 129, 132 accidental state, 12, 141, 142 Actor, 134136, 139 Ada, 11 ADT, 2123, 25, 30, 89, 111 Aggregation, 128 Ajax, 153 Algebra, 22, 35, 141, 142 Algorithmus, 22, 52, 81, 94 Aliasing, 71, 72 Annotation, 111, 120 Anweisung, 11, 15, 19, 30, 32, 33, 41, 46, 63, 69, 77 Anwendersicht, 147 Any, 52 Applikationsserver, 147, 149 Arbeitsspeicher, 87 Arc, 147 Architektur, 5, 77, 126, 141143, 147, 148, 155 Architekturkomponenten, 155 Argumente, 16, 25, 31, 35, 52, 64, 65, 86, 88, 93, 95, 96, 101, 104, 115, 119121, 163, 172 Argumentenliste, 80 Argumenttyp, 64 Array, 101, 105, 163 Assemblersprachen, 20 Assignment, 11, 12, 107 asynchronous, 136 Attributlisten, 149 Aufzhlungstypen, 8 Ausdruck, 10, 15, 16, 20, 38, 69, 7779, 82, 94, 104, 107, 108, 161, 165, 169, 172 Ausfhrungsgeschwindigkeit, 78, 150 Ausfhrungslogik, 94 Ausnahmebehandlung, 152, 165 Auswahlanweisungen, 14 Auswertungsreihenfolge, 79, 87 Auszeichnungssprache, 149 Axiome, 13 Backus, 11 Basisklasse, 49, 68, 106 Basisrelationen, 142 Batch, 73 Baumdarstellung, 169 Baumstruktur, 168 Bedingungen, 37 Befehl, 9, 14, 15, 20, 77, 78, 104, 162, 165 Beherrschbarkeit, 140 Belegung, 3436, 63, 118 Benutzer, 20, 21, 91, 92 Benutzerprozesse, 88 BETA, 115 Betriebssystem, 91, 151 Bezeichner, 83, 101, 107, 119 Bibliothek, 55, 91, 134, 136 Bildmenge, 8, 78, 79 Bildschirm, 91 binary, 149 Bindefunktion, 93, 94 Binden, 47, 48, 64, 68, 69, 104, 119, 149, 150 Bit, 148 Block, 84, 135, 165 Blog, 52, 127, 139 bottlenecks, 151

181

INDEX Bruegge, 29 Burger, 147, 148, 150, 155 business, 70, 141 Businesslogik, 173 by-name, 83, 84 Byte, 126 Calculus, 115 Callback-Funktionen, 82 CamelCase, 6 Cannon, 126 CAR, 148 CDR, 148 Client, 89, 106, 151 Clojure, 1, 64, 125, 126, 139, 140, 144, 147, 151 CLOS, 64, 65 Closure, 82, 98 Cobol, 144, 152 Codeumfang, 1, 66, 67, 142, 154 collections, 60, 139 colon-equals, 11, 38 Comet, 153 Compiler, 6, 7, 13, 32, 46, 81, 87, 102, 104, 106, 111, 112, 122, 126, 127, 133, 149, 152, 156 Complexity, 12, 67, 141 component, 128 Computerhardware, 77, 91 cons, 99, 148 Consortium, 167 constraint, 35 constructor, 50, 52 Container, 17, 63, 114 Continuations, 92 Control, 11, 12, 35, 72, 141, 142 Cook, 23, 111 CPU, 5 Currying, 8285 cycles, 145, 146 cygwin, 151 Datei, 148, 161 Datenabstraktion, 1923, 25, 29, 30, 32, 41, 62, 63, 67, 68, 70, 72, 73, 89, 103 Datenbank, 87, 107, 147150 Datenbankzugri, 173 Datenu, 88 Datenformat, 172 Datenhierarchien, 142 Datenkapselung, 94 Datenkollektionen, 19 Datenmatrix, 89 Datenmodellierung, 142 Datenobjekte, 111 Datenreprsentation, 142 Datenstruktur, 22, 86, 98, 99, 169, 173 Datentyp, 8, 22, 23, 25, 60, 93, 103 Deadlock, 134 Debugging, 126 Deklarationstypen, 111 deklarativ, 13, 143 denotational, 156 dependency, 131 deprecated, 156 derived, 70 Designentscheidung, 11 Desktop, 153 deterministisch, 88, 91, 134 Dienstleister, 41 Division, 165 DOM, 168 Double, 145 DTD, 167 Ducktyping, 122 Dynamisch, 112, 113, 148, 149 Eclipse, 126 Edinburgh, 115 Ezienz, 150 eindimensional, 69, 70 Einfachheit, 72, 143, 147, 155, 156 Eingabedaten, 12 Eingabekanal, 88 Eingabeparameter, 121 Einheitsfunktion, 93

182

INDEX Einschlu, 97 Einstiegshrden, 154 Eleganz, 65, 91 Element, 10, 34, 42, 52, 59, 63, 78, 79, 98, 104, 119, 169, 171, 172 Emacs, 126, 149 Empfngerobjekt, 65 encapsulation, 34 ENSIME, 126, 127 enterprise, 126 Entitt, 135 Entwicklerproduktivitt, 125, 127 Entwicklungswerkzeuge, 125 Entwurfsprinzipien, 152 Entwurfsziele, 152 enumeration, 8 EPFL, 1 Erfolgsfaktoren, 87 Ergebniswert, 20, 81, 165 Error, 25 essentiell, 9, 13 Evaluierung, 82 Excel-Tabelle, 101 Expression, 10, 78 Expressivitt, 143, 144, 150, 155 extensible, 167 extensional, 35 Extraktor, 106 Fabrikmethode, 104 Faktor, 12 Fakulttsfunktion, 81 Fehlerbehandlung, 93 Fehlermeldung, 14, 84, 118, 152 Feld, 32, 59, 128 le, 35 Filter, 107, 172 Fliekommazahl, 21, 22 FORTRAN, 21, 77, 144, 152 FP, 1 Frameworks, 128, 153, 157 FRP, 69, 70, 80, 140143, 155 Funktionsabschlsse, 96 Funktionsabstraktion, 20 Funktionsargument, 83 Funktionsaufruf, 78, 82, 84 Funktionsausdrcke, 96 Funktionsbegri, 91 Funktionsdenition, 79, 90 Funktionsdierential, 92 Funktionseinschlu, 97 Funktionsliteral, 97 Funktionsname, 79 Funktionsparameter, 79, 97, 120 Funktionsrumpf, 81 Funktionstheorie, 77, 81 Funktionstypen, 120 Funktionswert, 82, 84 Funktor, 115, 116 Geheimnisprinzip, 22, 72, 143 Generalisierung, 15, 42, 44, 104 Generator, 107, 108 Generics, 115 Geschftslogik, 150 Getter, 37, 50 Ghosh, 132, 134 Gleichheitszeichen, 10, 11 Gleichung, 90 GNU/Linux, 149, 151 Google, 126 goto, 68 Graham, 1, 144148, 150, 151, 156 Graphen, 52 Guards, 105 GUI, 150 Hacker, 156 Hardwarearchitektur, 2, 5 Hardwareindustrie, 54, 127 Hardwarekomponenten, 128 hash-Code, 146, 149 Haskell, 82, 83, 140 Haufenspeicher, 9 Hauptprogramm, 20 Hauptspeicher-Zelle, 7 head, 25, 119, 148 heap, 9

183

INDEX Helferfunktionen, 96 Hickley, 139, 140 Hiding, 22, 70, 72, 92 Hierarchie, 41, 42, 60, 62, 71, 102 Hierarchiebaum, 60 Hierarchieebenen, 42 Hierarchiestufen, 42 higher-order, 83, 115 Hilfsfunktionen, 95 Hoare, 105 HTML Formulare, 149, 150 Huang, 60 IBM, 152 IDE-Plugins, 125, 126, 157 immutable, 12, 90, 98, 140, 168 Imperative, 2, 5, 12, 34, 77, 78, 88, 91, 92, 139, 140, 143, 151, 154, 173 Implementierung, 8, 21, 22, 25, 49, 50, 55, 59, 62, 81, 89, 102, 103, 116, 129, 149, 153 implizit, 13, 41, 51, 60, 78, 104, 147, 148 Importmechanismen, 63 impure, 140 Indexmenge, 8 Indizierung, 100 Industriestandards, 125 Inezienz, 91 innity, 147 inheritance, 35, 48, 131, 139 Initialisierung, 32, 87 Input, 87, 91 Inspektion, 13, 102, 126, 149 Instanz, 32, 39, 64, 96, 106, 107, 118, 119, 121, 169 Instanzvariable, 34, 35, 41, 47, 71 Integer, 7, 23, 25, 32, 38, 55, 79, 89, 97, 98, 111, 118 IntelliJ-IDEA, 126 intensional, 35 Interface, 21, 49, 50, 62, 115 Internet, 145 Interpreter, 15, 111, 149, 161 invariant, 99, 122 inverse, 83 Investitionssicherung, 126 invocation, 52 Iry, 93, 140 Iteration, 16, 80, 107 Jahrhundertsprache, 1, 147, 148, 150, 151 Java, 6, 911, 14, 15, 32, 33, 36, 38 40, 46, 47, 4951, 56, 60, 6264, 73, 81, 95, 99, 104106, 112115, 121, 122, 125128, 134, 140, 144, 147, 151, 154, 157, 161, 165, 173 Job, 151 JVM, 60, 105, 122, 125, 126, 134, 140, 144, 151, 154 Kapselung, 20, 21, 37, 71 Klassenabstraktion, 132 Klassenargumente, 105 Klassenbibliothek, 118, 120 Klassendenition, 52, 94, 106, 114 Klassenelemente, 34 Klassenhierarchie, 34, 45, 60, 65, 69, 102, 119, 133 Klassenkonstruktor, 32 Klassenparameter, 32, 33, 35, 41, 47, 48, 59, 106, 118, 120, 128 Klassenrumpf, 32 Klassentyp, 105 Klausel, 14, 48, 165 Knuth, 150 Kollektionen, 17, 60, 90, 91, 122, 139 Kommandozeile, 162 kommutativ, 113 Kompilierung, 126 Komplexitt, 1, 2, 9, 11, 12, 14, 19, 34, 37, 6670, 72, 80, 113, 139142, 152, 154, 155 Komponentensysteme, 140 Kompositionsbeziehung, 42 Kompositionsmechanismen, 157 Kongurierung, 63 Konstruktoraufrufe, 52, 59

184

INDEX Kontravarianz, 119, 120 Kontrollabstraktion, 83 Kontrollanweisungen, 2, 12 Kontrollu, 1, 1214, 66, 69, 78, 86, 142, 143 Kontrollmechanismus, 16 Kontrollstruktur, 13, 17 Kopplung, 54, 62, 128 Kovarianz, 99, 119, 120 Kraftpunkt, 92 Lambda-Ausdruck, 81, 82 Laufzeitumgebung, 125, 147, 149 Lausanne, 1 lazy, 86 Lebensdauer, 6, 33, 148 Leibnitz, 92 Lernkurve, 154 Lesbarkeit, 20, 94, 112 lexikalisch, 150 Lift, 152, 153 Linearisierung, 51, 52, 54, 55, 59, 129 Linux, 151 Lisp, 64, 92, 98, 102, 139, 143145, 147 150 Listenelement, 38, 101 Listenmethoden, 101 Listenobjekt, 119 Listenoperationen, 101 Listenverarbeitung, 98 Literale, 95, 96, 99 Logik, 80, 93, 142 Lokalittsprinzip, 68, 150 Loop, 93, 107 Lsungsbereichsmodell, 29 Mailbox, 135, 136 Mainstream-Sprachen, 127, 128, 144, 173 Makro, 64 Maschinensprachen, 2 Maschinenwrter, 148 Mathematik, 19, 77, 80 Matrix, 23, 25, 32, 89 Maximalgeschwindigkeit, 155 Maeinheiten, 37 McBeath, 52 Mehrfachvererbung, 4851, 131 Metasprache, 167 Methodenaufruf, 47, 60, 112, 129 Methodendenition, 32, 118 Methodenname, 83 Methodenparameter, 117, 118 Methodenrumpf, 34, 37, 83 Methodenversand, 69 Minimalismus, 148 Mixin-Mechanismus, 50, 60, 67, 68, 70, 73, 129, 131, 132, 154, 157 ML, 83, 115 Modellierung, 5, 8, 34, 42, 48, 87, 88, 122, 125, 142 Modelltransformationen, 29 Modikator, 40, 86, 87 Modul, 21, 63, 71, 115, 126 Monade, 9294 Moore, 145 Moseley, 1, 1113, 3437, 6673, 80, 88, 140143, 155, 156 Multi-Methoden, 65 Mustervergleich, 101, 103, 105 mutable, 90, 139, 140 Mchtigkeit, 155 Nachrichtenversand, 128, 135 Namenskonikte, 129 Namenskonventionen, 6, 149 Namensraum, 9, 59, 64 Namensschutz, 71 Namensquivalenz, 114 Nebenlugkeit, 134 NetBeans, 126 Nil, 25, 62, 148 None, 62 Nothing, 62 Null, 25, 60, 62 Objekt-funktional, 125, 147 Objektgeechte, 70

185

INDEX Odersky, 1, 6, 11, 86, 115, 122, 128, 139, 140 OOP, 1, 34 operation-centric, 65 Operator, 10, 38, 55, 56, 99 Optimierung, 8, 12 orthogonal, 156 Output, 87 overloading, 139 Pakethierarchie, 63 Paradigma, 2, 29, 36, 77, 81, 125, 154 Parameterliste, 79, 8284, 104 Parameterwert, 97 Pascal, 11, 21 Pattern, 50, 89, 102, 103, 105, 115, 136, 172, 173 Pepper, 91, 92 Performance-Flaschenhals, 12, 141, 150, 153 Perl, 126 Persistence, 131 Philosophie, 19, 90, 92, 136, 143 PicoLisp, 68, 147151, 155 Pilog, 149 PL/I, 152 Plattform, 105, 151, 167 Platzhalter-Unterstrich, 83, 96 Platzhalter-Unterstrich, 104 Plugin, 126 Pollak, 152 Polymorphismus, 38, 47, 48, 65 pool, 9, 135 Popularitt, 125, 157 Portabilitt, 151 Praktikabilitt, 12, 78, 144 Proler, 150, 151, 156 Programmanalyse, 69 Programmanweisungen, 9, 12, 13 Programmfehler, 66, 114 Programmiermodi, 153 Programmierparadigma, 91, 127, 139, 155, 172 Programmierstil, 34, 98, 149, 154, 156 Programmlogik, 30, 32, 112 Prolog, 14, 149 Prozedurabstraktion, 20 Prozeduraufruf, 77 Prozesse, 5, 19, 88, 128, 133, 134 Prozeabstraktion, 20 Prdikat, 25 Python, 125, 148, 149 Qualittsproblem, 112 Redundanzen, 33 Refactoring, 126 Referenzgleichheit, 36 Referenzsemantik, 7 Referenztypen, 52, 56, 60, 118 Rekursion, 7981 Relational, 69, 70, 80, 140, 141, 155 Renaissance, 2 Reprsentationstypen, 111 Reprsentationswerte, 89 Ressourcen, 134 Return-Anweisung, 62, 69, 80 Ruby, 122, 125 Rckgabewert, 38, 79, 95, 98, 103, 119, 165 Saint-Exupry, 151 Scala, 1, 2, 611, 1417, 23, 3133, 36 38, 41, 45, 46, 5052, 5456, 58 60, 6264, 6670, 72, 73, 79, 81, 83, 84, 86, 87, 91, 9399, 101 103, 105107, 111113, 115, 116, 118122, 125129, 131136, 139, 140, 143, 145, 147, 151157, 161, 163, 165, 168170, 172174 Scheme, 102 Schleife, 16, 17, 83, 107 Schlsselwort, 33, 34, 3840, 45, 46, 51, 90, 95, 104, 105, 165 Schnittstelle, 21, 37, 49, 62, 115, 128 Scope, 6, 9, 150 Sebesta, 5, 7 Seiteneekte, 20, 46, 87, 98 Selektor, 14

186

INDEX selftype, 67, 129132 Semantik, 13 Semaphoren, 134 Sequenz, 13, 105, 107, 148, 169 Serverplattform, 151 Service-Orientierung, 127, 132, 133 Setter, 37, 50 Sichtbarkeitsregeln, 9, 38 Sichten, 69, 70 Signatur, 55, 112, 115, 116, 161, 163 Singleton, 62, 83, 116, 161 Skalierbarkeit, 72, 73, 132, 140, 154 Skalpell, 147 Slots, 64 Smalltalk, 42, 59, 6365, 111, 112, 125 SML, 115, 116 Softwarekrise, 80, 155 Spaghetti-Code, 68 Speicheradresse, 7 Speicherlayout, 112 Speicherort, 9 Speicherzelle, 9, 155 Sperren, 134 Spezikation, 22, 23, 131 Spiewak, 85, 139 Sprachdesign, 1, 147 Sprungziel, 69 SQL-Datenbankabfrage, 172 stack, 9 State, 34, 70, 80, 88, 141, 142 Statement, 11, 77 Statisch, 79, 102, 112, 113, 128, 157 Statistik-Software, 102 Steimann, 42, 6567, 7173, 113 Stellenanzeigen, 125, 144 STM, 139 Strings, 101, 111, 118, 146, 148, 168 Strukturquivalenz, 113 Subklasse, 34, 4649, 119 Substituierbarkeit, 6668 Subtypbeziehung, 119 Subtyping, 66, 67, 114 Suchprobleme, 73 Superklasse, 44, 4649, 51, 52, 60, 66, 103, 105, 119, 129, 169 Supertyp, 66, 114, 118121 Symbol, 11, 148, 149 Synonyme, 96 Syntax, 2, 20, 41, 51, 9496, 107, 143, 147, 150, 154, 156, 157 Syska, 83 Systemanalyse, 19 Systematik, 42 Systemlogik, 142 Systemressourcen, 134 Teilmengenbeziehung, 114 Thoughtworks, 125, 126, 144 Thread, 135, 136, 140 Top-Down-Verfahren, 22 Trait, 5052, 55, 59, 115, 118, 120, 129, 131, 132, 161, 163 Transaktion, 88 transient, 148 transitiv, 113, 114 Transparenz, 79 Tripel, 8, 93 Tupel, 8, 78, 101, 102, 105 Turingmaschine, 77 Typabstraktion, 55, 67, 68, 117, 118 Typannotation, 111, 112 Typdenition, 22, 114 Typdeklaration, 93 Typeinschrnkung, 66, 113 Typerweiterung, 66, 113, 114 Typfehler, 106, 114 Typhierarchien, 64 Typinferenz, 2, 113 Typinformation, 128, 148, 149 Typinvarianten, 114 Typisierungsregeln, 115 Typkonformitt, 114 Typparameter, 118, 119 Typschranke, 118, 119 Typsicherheit, 125, 173 Typsystem, 2, 111113, 115, 116, 120 122, 153, 154

187

INDEX Typtest, 114 Typumwandlung, 114 Typvarianz, 120 Typzuweisungen, 122 Typquivalenz, 113, 114 Unendlichkeit, 86 Unit, 19, 50, 62 Unterbereichstypen, 8 Unterklasse, 60 Unternehmenssoftware, 150 Unterprogrammaufruf, 68 Unterstrich, 6, 15, 83, 96, 186 Unvernderlichkeit, 99 Ursprungsmenge, 8, 78, 79, 82 Variable, 7, 9, 10, 12, 37, 46, 48, 59, 69, 71, 97, 104, 106, 112, 114, 121, 172 Varianzen, 118, 119 Vererbungsbeziehung, 49, 60 Vererbungshierarchie, 45, 49, 52, 67, 69, 70, 105, 119 Vererbungsmechanismus, 68 Vergleichsoperatoren, 55 Verkapselung, 62 Verkettung, 10 Verklemmung, 134 Verknpfung, 150 Verschachtelung, 105 Verschattung, 92 Verschwendung, 146 Vertrag, 68 Verweissemantik, 71 VM, 147, 148, 151 void, 62 W3C, 167, 168 Wahrheitswerte, 8 Wahrscheinlichkeit, 66 Wampler, 52, 85, 107, 118, 120, 129131 Wartbarkeit, 94 Watt, 5, 19, 78, 86 Web, 128, 151153, 167, 172, 173 Werkzeuguntersttzung, 126 Wertetyp, 60, 118 Wertgleichheit, 35 Wertsemantik, 7 while, 128, 140 Wiederholung, 5, 12, 79, 80 Wiederverwendbarkeit, 20 Wikipedia, 12 Wildcard, 104 Wilhelmi, 83 Windows, 151 XML, 2, 127, 167172 XQuery, 173 XSLT, 173 Zeigerstrukturen, 149 Zelle, 7, 9, 23, 148 Zenger, 128 Zugrismodikator, 39, 40, 46, 95 Zugrispfade, 142 Zugrisregeln, 38 Zusicherungen, 37 Zustand, 1, 5, 11, 12, 29, 3134, 36, 46, 55, 56, 59, 66, 67, 73, 80, 87, 88, 139, 142, 149, 154, 172, 173 Zuweisungskompatibilitt, 66, 113, 114, 121 Zuweisungsoperation, 1012, 34

188