Durchstarten mit Scala

Werbung
Heiko Seeberger
Durchstarten mit Scala
Tutorial für Einsteiger
Heiko Seeberger
Durchstarten mit Scala. Tutorial für Einsteiger
(2. aktualisierte Auflage)
ISBN: 978-3-86802-343-5
© 2015 entwickler.press
Ein Imprint der Software & Support Media GmbH
Bibliografische Information Der Deutschen Bibliothek
Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen
Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über
http://dnb.ddb.de abrufbar.
Ihr Kontakt zum Verlag und Lektorat:
Software & Support Media GmbH
entwickler.press
Darmstädter Landstraße 108
60598 Frankfurt am Main
Tel.: +49 (0)69 630089-0
Fax: +49 (0)69 630089-89
[email protected]
http://www.entwickler-press.de
Lektorat: Corinna Neu
Korrektorat: Frauke Pesch, Jennifer Diener
Copy-Editor: Nicole Bechtel
Satz: Dominique Kalbassi
Umschlaggestaltung: Maria Rudi
Belichtung, Druck & Bindung: Media-Print Informationstechnologie GmbH, Paderborn
Cover: © kycstudio | istockphoto.com
Alle Rechte, auch für Übersetzungen, sind vorbehalten. Reproduktion jeglicher Art (Fotokopie,
Nachdruck, Mikrofilm, Erfassung auf elektronischen Datenträgern oder anderen Verfahren) nur
mit schriftlicher Genehmigung des Verlags. Jegliche Haftung für die Richtigkeit des gesamten
Werks kann, trotz sorgfältiger Prüfung durch Autor und Verlag, nicht übernommen werden. Die
im Buch genannten Produkte, Warenzeichen und Firmennamen sind in der Regel durch deren
Inhaber geschützt.
I
Inhaltsverzeichnis
Vorwort9
1 Warum Scala?
11
1.1 Was ist Scala?
11
1.2 Warum Scala statt Java?
12
1.3 Warum Scala statt Groovy, Clojure und Co?
16
2Entwicklungsumgebung
2.1Kommandozeilenwerkzeuge
17
17
2.1.1scalac
18
2.1.2scala
19
2.1.3scaladoc
20
2.2sbt
21
2.3IDE
24
3 Das Fallbeispiel ScalaTrain
27
4 Erste Gehversuche in der REPL
29
4.1Variablen
29
4.1.1 Unveränderliche Variablen
29
4.1.2 Veränderliche Variablen
31
4.2Methoden
31
4.2.1 Alles hat ein Ergebnis
32
4.2.2Unit-Methoden
33
4.3Funktionen
5 Grundlagen der Objektorientierung
34
37
5.1 Vorbereitung: Projekt initialisieren
37
5.2Klassen
39
5.2.1 Klassenparameter und Konstruktoren
39
5.2.2Felder
41
5.2.3Methoden
44
5.2.4 Benannte Argumente und Standardwerte
48
Durchstarten mit Scala
5
Inhaltsverzeichnis
5.3 Pakete und Sichtbarkeit
49
5.3.1 Verschachtelte Pakete
49
5.3.2Imports
51
5.3.3Sichtbarkeit
53
5.4 Singleton Objects
53
5.4.1 Companion Objects
54
5.4.2Predef
54
5.5 Case Classes
55
5.6 Projektcode: aktueller Stand
58
6 Testen von Scala-Programmen
59
6.1 Testabdeckung mit scoverage
59
6.2 Unit Tests mit ScalaTest
60
6.2.1WordSpec
6.2.2Matcher
62
6.3 Testdaten mit ScalaCheck
64
6.4 Projektcode: aktueller Stand
67
7 Erste Schritte mit der funktionalen Programmierung
7.1 Scala Collections
69
69
7.1.1Klassenhierarchie
70
7.1.2 Collection-Instanzen erzeugen
71
7.1.3Typparameter
72
7.1.4Tupel
73
7.1.5 Unveränderliche und veränderliche Collections
74
7.1.6 Collections in ScalaTrain
76
7.2 Funktionale Collections
77
7.2.1Funktionsliterale
77
7.2.2Funktionstypen
79
7.2.3 Funktionale Collections in ScalaTrain
81
7.2.4 „map“, „flatMap“ und „filter“ im Detail
84
7.3 For-Ausdrücke und For-Schleifen
87
7.3.1For-Ausdrücke
89
7.3.2 For-Schleifen und foreach
92
7.4 Projektcode: aktueller Stand
6
61
93
Inhaltsverzeichnis
8 Vererbung und Traits
8.1Vererbung
95
95
8.1.1 Unterklassen mit „extends“ definieren
95
8.1.2 Felder und Methoden überschreiben
97
8.1.3 Abstrakte Klassen
99
8.1.4Scala-Typhierarchie
8.2Traits
103
105
8.2.1 Traits hineinmixen
106
8.2.2Linearisierung
108
8.2.3 Beispiel: „Ordered“ implementieren
110
8.2.4 Einschub: By-Name Parameters
112
8.3 Projektcode: aktueller Stand
9 Pattern Matching
116
119
9.1Match-Ausdrücke
119
9.2 Welche Patterns gibt es?
120
9.2.1 Wildcard Pattern
120
9.2.2 Constant Pattern
120
9.2.3 Variable Pattern und Typed Pattern
121
9.2.4 Tuple Pattern
121
9.2.5 Constructor Pattern
121
9.2.6 Sequence Pattern
122
9.3 Pattern Guards und Variable Binding
123
9.4 Pattern Matching außerhalb von Match-Ausdrücken
124
9.5 Projektcode: aktueller Stand
126
10Implicits
10.1 Implicit Conversions
129
129
10.1.1Implicit Conversions zum Expected Type
130
10.1.2Implicit Conversions des Receivers
134
10.2 Implicit Classes
134
10.3 Implicit Parameters
136
10.4 Type Classes
139
10.5 Projektcode: aktueller Stand
142
Durchstarten mit Scala
7
Inhaltsverzeichnis
11 Zielbahnhof – Fortgeschrittene Konzepte
145
11.1Rekursion
145
11.2 Upper Bounds und Context Bounds
148
11.2.1Einschub: Package Objects
148
11.2.2Einschub: Varianz
149
11.2.3Upper Bounds
150
11.2.4Context Bounds
151
11.3 Vertiefung objektfunktionale Programmierung
153
11.3.1Problemstellung
153
11.3.2Lösungsansatz
154
11.3.3Etappen und Teilstrecken
156
11.3.4Verbindungen ermitteln
158
11.4 Projektcode: aktueller Stand
12Scala-Bibliotheken
160
163
12.1 Validieren mit Scalactic
163
12.2 Akka HTTP
168
12.3 Projektcode: finaler Stand
174
Stichwortverzeichnis179
8
V
Vorwort
Als ich mich vor etwa fünf Jahren zusammen mit meinem Kollegen Roman Roelofsen
dazu aufmachte, ein Scala-Buch zu schreiben, war das ein Schuss ins Blaue. Schließlich
war Scala damals gerade am Gipfel des Hypezyklus angekommen und es war unklar, wie
es langfristig weitergehen würde. Nichtsdestotrotz – oder vielleicht gerade deswegen –
schrieben wir ein Scala-Buch, das es in dieser Form bisher noch nicht gab: zum einen auf
Deutsch und zum anderen als praxisorientiertes Tutorial ausgelegt.
Natürlich folgte das typische Tal der Enttäuschungen, denn mit Ausnahme weniger Märkte wie dem Silicon Valley und London wurde Scala lange Zeit nicht in nennenswertem
Umfang eingesetzt; insbesondere galt dies für Deutschland und den weiteren deutschsprachigen Markt. Dennoch wurde Scala in dieser Zeit kontinuierlich weiterentwickelt,
nicht nur, aber in starkem Maß durch die Firma Typesafe, der ich mich kurz nach deren
Gründung im Jahr 2011 anschloss. Und selbst wenn der Durchbruch noch eine Weile auf
sich warten ließ, so konnte ich in dieser Zeit zumindest von der Fallstudie profitieren, die
sich durch dieses Buch wie ein roter Faden zieht, denn ich verwendete sie für das offizielle
Schulungsmaterial, das ich für Typesafe erstellte.
Inzwischen – spätestens seit den fantastischen Scala Days im Jahr 2014 in Berlin – ist Scala
auch in Deutschland und in den meisten anderen Märkten angekommen. Dennoch war
ich positiv überrascht, als ich vor einem halben Jahr erfuhr, dass die erste Auflage des
Buchs vergriffen sei. Da ich mich weiterhin – auch und gerade nach meinem Wechsel zu
codecentric – stark in Sachen Scala engagiere, musste ich nicht lange überlegen und entschied mich für eine zweite, überarbeitete Auflage. Natürlich hat das tolle Feedback, das
ich von etlichen Lesern bekommen habe, zu dieser Entscheidung beigetragen.
Im Kern ist die vorliegende zweite Auflage sehr ähnlich wie die erste, denn an den Grundlagen von Scala hat sich nicht viel geändert. Selbstverständlich ist die Werkzeugunterstützung viel besser und einfacher geworden, sodass der erste Teil verändert und gekürzt
werden konnte. Auch hat sich viel in der Landschaft der Bibliotheken getan. Zum Beispiel
hat Lift ausgedient; heute verwendet man Play oder Akka HTTP. Daher bedurfte es auch
einer Neugestaltung des letzten Teils. Was auf jeden Fall gleich geblieben ist, sind der praxisorientierte Charakter und das durchgängige Fallbeispiel sowie die Freude des Autors
an Scala. Ich hoffe, dass ich diese mit dem vorliegenden Buch ebenso vermitteln kann wie
das Programmieren mit Scala.
Heiko Seeberger
Oktober 2015
Durchstarten mit Scala
9
1
1
Warum Scala?
Ja, warum sollten wir uns eigentlich mit Scala beschäftigen? Mit Blick auf die schiere Vielzahl
an Programmiersprachen eine berechtigte Frage. Wir können diese auch anders formulieren, und zwar: Welche Vorteile bringt Scala gegenüber anderen Programmiersprachen?
Mit der Antwort wollen wir es uns hier ein bisschen einfacher machen, indem wir
ausschließlich die Java-Plattform betrachten. Das bedeutet, dass wir nur solche
Programmiersprachen unter die Lupe nehmen, die auf der Java Virtual Machine (JVM)
laufen. Diese Einschränkung halten wir deswegen für legitim, weil die Java-Plattform
sehr weit verbreitet ist und gerade im Bereich der Unternehmensanwendungen eine
durchaus dominante Stellung innehat. Gute Gründe hierfür sind – ohne Anspruch auf
Vollständigkeit – das Write-once-run-everywhere-Prinzip1 sowie die große Menge an verfügbaren Bibliotheken für nahezu alle denkbaren Anwendungsfälle.
1.1
Was ist Scala?
Vor der Frage nach dem Warum steht erst einmal diejenige nach dem Was. Auf einen Satz
kondensiert lautet unsere Antwort: Scala ist eine moderne und dennoch reife, objektfunktionale, praxisorientierte, statisch typisierte und trotzdem leichtgewichtige Sprache für
die JVM, die vollständig kompatibel zu Java ist. Wir wollen im Folgenden kurz ein paar
dieser Eigenschaften herauspicken und genauer erläutern. Alles Weitere bzw. sich ein
Bild von Scala zu machen, überlassen wir dann dem Leser für den weiteren Verlauf dieses
Buchs.
Scala wurde von Martin Odersky, Professor an der Schweizer Hochschule EPFL2, erfunden, der zuvor bereits an wichtigen konzeptionellen Neuerungen für Java gearbeitet hatte, zum Beispiel an den Generics3. Die erste Version wurde bereits 20044 veröffentlicht,
sodass wir bereits auf über ein Jahrzehnt mit Scala zurückblicken können.
Anfangs, als die Nutzerbasis noch nicht so groß war, leistete sich Scala bei neuen Versionen –
auch bei so genannten Minor-Releases – immer wieder binäre Inkompatibilitäten zu
vorherigen Versionen. Aber spätestens seit der Version 2.9, die zeitlich ungefähr mit der
Gründung der Firma Typesafe5, die kommerziell hinter Scala und anderen Technologien
1
2
3
4
5
https://en.wikipedia.org/wiki/Write_once%2C_run_anywhere
http://www.epfl.ch
https://en.wikipedia.org/wiki/Generics_in_Java
http://article.gmane.org/gmane.comp.lang.scala/17
http://www.typesafe.com
Durchstarten mit Scala
11
1 – Warum Scala?
wie Akka6 und Play steht, zusammenfällt, ist das Geschichte: Seitdem sind alle MinorReleases mit derselben Major-Release-Nummer binär kompatibel, also z. B. 2.9.1 und
2.9.2. Nur bei Major-Releases, die 18 Monate oder länger auseinanderliegen, wird es notwendig, ein gesamtes Projekt einschließlich aller Bibliotheken auf die neue Version zu
heben. Aber auch das ist in der Regel ein Einfaches, weil dank der Community Builds7 die
meisten populären Bibliotheken quasi gleichzeitig mit einem Major-Release von Scala für
genau dieses veröffentlicht werden.
Scala kann also getrost als reife Sprache betrachtet werden. Zur weiteren Charakterisierung
ist vermutlich der hybride Charakter der Sprache das hervorstechendste Merkmal: Scala
ist einerseits objektorientiert8, und das sogar viel stringenter als Java. Andererseits ermöglicht Scala funktionale Programmierung9, also „so etwas mit Lambdas“, die ja spätestens
seit Java 8 auch unter Java-Entwicklern steigende Bekanntheit genießen. Scala bietet nicht
nur das Beste aus beiden Welten, sondern vereinigt die zwei Paradigmen weitreichend zu
einer objektfunktionalen Sprache aus einem Guss.
Abschließend noch das aus unserer Sicht wichtigste Kriterium für den großen Erfolg von
Scala: Wir können aus Scala heraus jeglichen Java-Code nutzen, egal ob unsere liebgewonnenen Bibliotheken für Logging, Persistenz etc. oder unsere eigenen Java-Projekte. Durch
diese Abwärtskompatibilität müssen wir nicht bei Null anfangen, sondern können unsere
bestehenden Java-Aktiva ver- und aufwerten.
1.2
Warum Scala statt Java?
Das führt uns direkt zur Frage, warum wir uns mit Scala beschäftigen sollten, wo es doch
Java gibt. Die Antwort fällt uns sehr leicht, und wir können sie sehr deutlich formulieren:
Im Vergleich zu Java können wir mit Scala sowohl produktiver arbeiten, als auch die
Qualität steigern. Abgesehen davon macht das Programmieren mit Scala einfach mehr
Spaß, wobei das natürlich eine subjektive Einschätzung aus unserer Sicht ist.
Zugegebenermaßen benötigen wir zunächst ein wenig Zeit, um mit Scala durchzustarten,
aber die langfristigen Vorteile überwiegen mit Sicherheit den Nachteil des Lernaufwands,
den jede neue Technologie mit sich bringt. Gerade Java-Programmierer sollten hierfür
ein offenes Ohr haben, denn auch Java ist noch recht jung, weshalb nicht wenige von uns
zuvor von anderen Programmiersprachen auf Java umgestiegen sind. Welches sind nun
die Gründe dafür, dass Scala Produktivität und Qualität im Vergleich zu Java zu steigern
vermag? Wir wollen hierfür die folgenden ins Feld führen: weniger Code und höheres
Abstraktionsniveau.
6
7
8
9
12
http://akka.io
https://github.com/scala/community-builds
https://de.wikipedia.org/wiki/Objektorientierung
http://de.wikipedia.org/wiki/Funktionale_Programmierung
Warum Scala statt Java?
Wie wir im weiteren Verlauf dieses Buchs sehen werden, benötigen wir mit Scala signifikant
weniger Code als mit Java, um ein Programm zu realisieren, das dieselben Anforderungen
erfüllt. Mit signifikant meinen wir mindestens eine Reduktion von 50 Prozent, möglicherweise bzw. situativ sogar bis zu 80 Prozent.
Es ist zwar richtig, dass moderne Java-Entwicklungsumgebungen beim Schreiben eines großen Teils des eingesparten Codes sehr gut unterstützen können; man denke zum
Beispiel an Funktionen zur Erzeugung von Zugriffsmethoden – also Getter und Setter –
wie sie jede moderne integrierte Entwicklungsumgebung (IDE) bietet. Somit sind wir
beim Codeschreiben nicht unbedingt schneller. Aber wenn wir an unseren Alltag als
Programmierer denken, dann werden die meisten von uns feststellen, dass wir insgesamt
mehr Zeit darauf verwenden, Code zu lesen und zu verstehen, als zu schreiben. Das betrifft nicht nur fremden Code, den wir übernehmen oder warten dürfen, sondern auch eigenen, den wir vor Wochen oder gar Monaten geschrieben haben und dessen Verständnis
uns heute einiges abverlangt.
Es gibt Untersuchungen10, die belegen, dass die Geschwindigkeit, wie schnell wir Code lesen und verstehen können, ganz entscheidend von der Codemenge bestimmt wird. Wenn
wir zum Beispiel an eine typische JavaBean denken, die quasi nur einen Datencontainer
für eine Handvoll Properties darstellt, dann sehen wir uns einer gewaltigen Menge an
semantisch wenig wertvollem Code gegenüber: Getter, die nur ein privates Feld zurückgeben. Oft auch Setter, die nur ein privates Feld verändern. Vermutlich auch mehrere
Konstruktoren, mit denen alle oder zumindest einige der Felder initialisiert werden können. Und nicht selten auch noch Implementierungen der Methoden equals, hashCode und
toString. Wenn wir nun diesen Code lesen und verstehen wollen, dann müssen wir im
Verstand einen Filter aktivieren, der all den überflüssigen Kitt ausblendet und nur die
eigentliche Intention durchlässt.
Ein explizites Beispiel gefällig? Wie wäre es mit einer Klasse für eine Person, die Vor- und
Nachname haben soll. Im Vorgriff auf die Prinzipien der funktionalen Programmierung
soll diese Person unveränderlich sein, d. h. einmal erzeugt können deren Attribute
nicht mehr verändert werden. Dieses Prinzip des Immutable Object11 ist natürlich auch
in Java bekannt, nicht erst seit dem Buch „Effective Java“12 von Joshua Bloch. Zunächst
zeigen wir den Java-Code, bei dem die Methoden equals und hashCode von der
Entwicklungsumgebung generiert wurden:
public class Person {
private final String firstName;
private final String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
10 http://infoscience.epfl.ch/record/138586/files/dubochet2009coco.pdf
11 http://en.wikipedia.org/wiki/Immutable_object
12 http://java.sun.com/docs/books/effective
Durchstarten mit Scala
13
1 – Warum Scala?
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (firstName != null ?
!firstName.equals(person.firstName) :
person.firstName != null) return false;
if (lastName != null ?
!lastName.equals(person.lastName) :
person.lastName != null) return false;
return true;
}
@Override
public int hashCode() {
int result = firstName != null ? firstName.hashCode() : 0;
result =
31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
}
Und nun zum Vergleich der äquivalente Scala-Code, der wirklich funktional vollständig
identisch ist, wie wir im Verlauf dieses Buchs noch sehen werden:
case class Person(firstName: String, lastName: String)
Nur eine einzige Codezeile in Scala im Vergleich zu – je nachdem wie wir zählen – zwanzig oder mehr in Java. Das liegt daran, dass wir nicht all die kleinen Details wie zum
Beispiel die Zugriffsmethoden oder equals und hashCode ausprogrammieren müssen,
sondern der Scala-Compiler diese Arbeit für uns macht. Zwar haben gerade erfahrene
Java-Entwickler einen besonders gut geschulten kognitiven Filter für irrelevante Details,
sodass wir beim Geschwindigkeitsvergleich für Lesen und Verstehen natürlich nicht von
einem Verhältnis 1:20 ausgehen dürfen. Aber sicher kann niemand ernsthaft bestreiten,
dass dieser Scala-Code nicht um Faktoren rascher aufgenommen werden kann als der
entsprechende Java-Code. Und nun stellen wir uns einmal ein umfangreicheres Beispiel
vor, bei dem wir den Java-Code ohne mehrfaches Blättern bzw. Scrollen gar nicht mehr
lesen können.
14
Warum Scala statt Java?
Weiter gibt es Aussagen13, dass zwischen der Codemenge und der Anzahl von Fehlern
ein direkter Zusammenhang besteht. Oder anders ausgedrückt: je mehr Code, desto
mehr Fehler. Unabhängig davon, ob das im Allgemeinen tatsächlich zutrifft, leuchtet das zumindest für unser Beispiel sofort ein. Denn der Java-Code hat ein niedriges
Abstraktionsniveau, geht also stark ins Detail, und da versteckt sich bekanntlich gerne
der eine oder andere Fehler. Man betrachte bloß obige Implementierungen von equals
und hashCode.
Im Hinblick auf das Abstraktionsniveau betrachten wir als Beispiel eine Liste von
Personen, die wir anhand ihrer Nachnamen sortieren möchten. In Java würden wir wohl –
ganz im guten alten imperativen Programmierstil – Hilfsvariablen anlegen und Schleifen
programmieren, innerhalb derer wir die Hilfsvariablen verändern. Wir würden uns also
im Detail darum kümmern, wie die Anforderung umzusetzen ist. In Scala hingegen machen wir das folgendermaßen, wobei wir davon ausgehen, dass wir eine Variable persons
haben, die eine Liste von Personen repräsentiert.
persons.sortWith((p1, p2) => p1.lastName < p2.lastName)
Bitte nicht erschrecken! Hier geht es nicht darum, diesen Scala-Code exakt zu verstehen.
Vielmehr wollen wir zwei Dinge deutlich machen. Erstens gibt es offenbar für ScalaCollections eine Methode sortWith, deren Bedeutung sich hoffentlich direkt aus dem
Namen erschließt. Und zweitens übergeben wir dieser Methode ein Stück Code, welches
offenbar die Sortierlogik enthält. Im Vorgriff auf spätere Kapitel: Es handelt sich hier um
ein Lambda – also einen Funktionswert – der zwei Personen als Argumente entgegennimmt und deren Nachnamen mit dem Kleiner-Operator vergleicht.
Auch wenn wir diesen Code jetzt noch nicht im Detail verstehen, können wir erkennen, dass ganz klar zum Ausdruck gebracht wird, was wir erreichen wollen: die Liste
von Personen anhand der Nachnamen sortieren. Um das Wie hingegen, das uns eigentlich gar nicht interessiert, brauchen wir uns auch nicht kümmern, denn das ist in der
Implementierung der Methode sortWith verborgen.
Durch solch mächtige Methoden in Verbindung mit dem Sprachmerkmal von Funktionen
erreichen wir in Scala eine höhere Abstraktionsebene, auf der wir uns nur noch um das
Was kümmern müssen. Dadurch, dass wir die Details des Wie weglassen können, sind wir
natürlich schneller und machen weniger Fehler, denn gerade die Details kosten Zeit und
bergen hohes Risiko, etwas falsch zu machen.
Selbstverständlich gibt es in Java seit der Version 8 auch Lambdas sowie das Streams
API, welches grundsätzlich denselben funktionalen Ansatz ermöglicht, wenngleich dieses API bei Weitem nicht so reichhaltig ist, wie die Scala-Collections und Java-Collections
zuerst zu Streams konvertiert werden müssen, um später mittels Collector wieder in eine
13http://en.wikipedia.org/wiki/Source_lines_of_code
Durchstarten mit Scala
15
1 – Warum Scala?
Collection transformiert zu werden. Aber der funktionale Ansatz geht bei Scala viel weiter, wie wir im Verlauf dieses Buchs sehen werden.
1.3
Warum Scala statt Groovy, Clojure und Co?
So weit, so gut, Scala bietet also Vorteile gegenüber Java. Aber wie sieht es denn mit den
anderen Programmiersprachen für die JVM aus, die in den letzten Jahren entstanden sind?
Deren populärste Vertreter sind zurzeit wohl Groovy14 und Clojure15. All diese Sprachen
haben ihre Stärken und setzen das eine oder andere innovative Konzept um, sodass wir
volles Verständnis haben, eine solche Sprache anstatt Java zu verwenden.
Wobei, es gibt da schon einen Punkt, bei dem wir skeptisch sind. Und zwar handelt es sich
bei den genannten Sprachen und vielen weiteren um dynamisch typisierte Sprachen. Mit
anderen Worten gibt es keine Überprüfung durch den Compiler, ob wir unsere Objekte
oder Funktionen korrekt verwenden. Das wird zwar manchmal sogar als Vorteil verkauft,
aber aus unserer Sicht ist es in den allermeisten Fällen genau anders herum: Für echte
Softwareprojekte möchten wir auf keinen Fall eine Programmiersprache verwenden, die
nicht statisch typisiert ist. Wir möchten nämlich viele Fehler bereits beim Compilieren
erkennen und vermeiden, anstatt diese zur Laufzeit zu entdecken.
Scala ist so eine statisch typisierte Sprache. Das bedeutet, dass wir einer Methode, die ein
Argument vom Typ String erwartet, kein Date übergeben können. Nicht nur, aber auch
darum geben wir Scala ganz klar den Vorzug vor etlichen anderen JVM-Sprachen.
14 http://www.groovy-lang.org
15 http://clojure.org
16
2
2
Entwicklungsumgebung
In diesem Kapitel kümmern wir uns um unser Handwerkszeug, das wir für die Programmierung mit Scala benötigen. Wie wir sehen werden, stehen uns verschiedene Möglichkeiten zur Verfügung, von der minimalistischen Kommandozeile bis hin zur komfortablen IDE. Wir werden für die Beispiele in diesem Buch eine Konstellation wählen, wie wir
sie auch regelmäßig in unseren echten Scala-Projekten einsetzen. Ganz konkret werden
wir meist mit einer Kombination aus dem Build-Werkzeug sbt und der IDE IntelliJ IDEA
arbeiten und manchmal auch – für schnelle Experimente – mit der von der Kommandozeile aus gestarteten REPL; die Details zu diesen Werkzeugen folgen unten. Allerdings
ermutigen wir den Leser ausdrücklich, eigene Experimente anzustellen, um die für ihn
am besten geeignete Wahl zu treffen.
Selbstverständlich wollen wir nicht nur Trockenschwimmen üben, sondern die verschiedenen Möglichkeiten anhand des Klassikers schlechthin demonstrieren, also am guten
alten „Hello World“. Dabei werden uns natürlich die ersten Scala-Sprachmerkmale begegnen, die wir jedoch erst in späteren Kapiteln im Detail erläutern werden.
2.1
Kommandozeilenwerkzeuge
Wer bisher ausschließlich auf den Komfort einer IDE gesetzt hat und daher jetzt geneigt
ist, dieses Kapitel zu überspringen, dem sei schon an dieser Stelle vorweg gesagt, dass
Scala – anders als Java – über eine interaktive Konsole verfügt, die sich für Experimente
und zum Testen ganz hervorragend eignet. Da dieses kleine aber feine Werkzeug aus dem
Alltag eines Scala-Entwicklers quasi nicht wegzudenken ist, empfehlen wir dringend,
sich zumindest das Kapitel 2.1.2 über scala zu Gemüte zu führen.
Bevor wir die einzelnen Scala-Werkzeuge für die Kommandozeile betrachten können,
müssen wir diese installieren. Dazu laden wir uns von der Scala-Website1 die aktuelle
Scala-Distribution als Archiv herunter, je nach Plattform im tgz- oder zip-Format. Zum
Zeitpunkt, da wir dieses Buch schreiben, handelt es sich dabei um die Version 2.11.7 –
spätere 2.11-Versionen sollten ohne Einschränkungen verwendbar sein.
Nun entpacken wir das Archiv und fügen unserem Pfad das Verzeichnis bin der ScalaDistribution hinzu. Alternativ zum eben beschriebenen Vorgehen stellen manche Paketmanager, zum Beispiel Homebrew2 für OS X, Installationspakete für Scala zur Verfügung.
1
2
http://www.scala-lang.org
http://brew.sh
Durchstarten mit Scala
17
2 – Entwicklungsumgebung
Im Resultat sollten wir jedenfalls – unabhängig vom Weg dorthin – in der Lage sein, das
Folgende auf der Kommandozeile nachzuvollziehen:
~$ scala -version
Scala code runner version 2.11.7 -- Copyright 2002-2013, LAMP/EPFL
2.1.1
scalac
Als Erstes wollen wir unsere Aufmerksamkeit auf den Scala-Compiler richten. Dafür benötigen wir natürlich Scala-Code zum Compilieren und das soll – wie oben angekündigt –
der Klassiker „Hello World“ sein:
object Hello {
def main(args: Array[String]): Unit = {
println("Hello World")
}
}
Wenn wir diesen Code betrachten, dann kommt uns einiges von Java bekannt vor, zum
Beispiel die Methode main mit ihren Argumenten oder die Ausgabe mittels println. Anderes ist komplett neu, zum Beispiel die Schlüsselworte object und def oder die fehlenden
Semikolons. Wir werden – wie versprochen – alle Scala-Sprachmerkmale in der nötigen
Tiefe erläutern, aber an dieser Stelle bleiben wir an der Oberfläche und gehen davon aus,
dass die Intention des Beispiels klar ist.
Also, wir öffnen einen Texteditor unserer Wahl, geben obigen Code ein und speichern das
Ganze in der Quelldatei Hello.scala. Tatsächlich ist es dem Scala-Compiler egal, wie wir die
Datei benennen, denn im Unterschied zu Java können wir anders heißende und sogar mehrere so genannte Compilation Units in einer Datei haben. Aber in den meisten Fällen bietet
sich die von Java her bekannte Konvention an, und hier wollen wir es ebenso handhaben.
Nun kommt der Scala-Compiler ins Spiel, der sich hinter dem Programm scalac im Verzeichnis bin der Scala-Distribution verbirgt:
$ scalac Hello.scala
Wenn wir uns nicht vertippt haben, dann sehen wir nach dem Compilieren erst einmal
nichts, d. h., der Compiler beendet seine Arbeit ohne Fehlermeldung. Aber wenn wir
einen Blick in das Dateisystem werfen, dann werden wir feststellen, dass die Quelldatei
Hello.scala nicht mehr alleine ist, sondern Gesellschaft in Gestalt von Hello.class und Hello$.
class bekommen hat. Offenbar war der Compiler fleißig und hat aus einer Quelldatei bzw.
der darin enthaltenen einen Compilation Unit – unser object Hello – zwei Dateien mit der
Endung .class erzeugt. Dieser Arbeitseifer liegt in unserem Fall in der speziellen Behandlung von objects begründet, doch dazu mehr in Kapitel 5.
18
Kommandozeilenwerkzeuge
Was wir ohne Zweifel erkennen können: Offenbar erzeugt der Scala-Compiler Java-Bytecode. Das war auch nicht anders zu erwarten, denn Scala ist ja eine Sprache für die JVM.
Aber es tut doch ganz gut, sich mit eigenen Augen davon zu überzeugen. Wem das Indiz
in Form der Dateiendung auf .class nicht ausreicht, der möge zum Standard-Java-Decompiler javap greifen. Hier das Resultat für die Datei Hello.class:
$ javap Hello
Compiled from "Hello.scala"
public final class Hello {
public static void main(java.lang.String[]);
}
Wie wir sehen, handelt es sich bei Hello.class um gültigen Java-Bytecode. Wir können auch
erkennen, dass die Übersetzung von Hello die Methode main genau so enthält, wie wir
sie für ein ausführbares Java-Objekt benötigen. Ohne in dieses zugegebenermaßen etwas
spezielle Thema zu tief einsteigen zu wollen, sei erwähnt, dass es auch einen Scala-Decompiler gibt, der erwartungsgemäß scalap heißt:
$ scalap Hello
object Hello extends scala.AnyRef {
def this() = { /* compiled code */ }
def main(args: scala.Array[scala.Predef.String]): scala.Unit = ...
}
Zurück zum Scala-Compiler! Je nach Leistung des verwendeten Rechners müsste aufgefallen sein, dass das Übersetzen unseres trivialen Scala-Codes schon eine Weile gedauert
hat. Wer das tatsächlich nicht so wahrgenommen hat, der möge ein vergleichbares JavaBeispiel mit javac übersetzen.
Natürlich gibt es gute Gründe dafür, warum der Scala-Compiler langsamer ist, als der Java-Compiler, auf die wir hier nicht eingehen wollen. Dennoch ist es schlicht und ergreifend
in der Praxis mindestens störend, wenn wir immer wieder lange warten müssen, während
scalac sich abmüht. Aus diesem Grund sollten wir ein Build-Werkzeug wie sbt verwenden,
welches inkrementelles – und dadurch wesentlich schnelleres – Compilieren beherrscht.
2.1.2
scala
Nachdem wir nun wissen, wie wir unser „Hello World“ compilieren können, stellt sich
die Frage, wie wir es ausführen können. Wenn wir uns ins Gedächtnis rufen, dass der Scala-Compiler Java-Bytecode erzeugt, dann liegt die Vermutung nahe, dass wir einfach java
verwenden können. Und das ist korrekt, wobei wir darauf achten müssen, zusätzlich zum
aktuellen Verzeichnis, das unser „Hello World“ enthält, die Scala-Standardbibliothek in
den Klassenpfad zu geben:
$ java -cp .:.../scala-library.jar Hello
Hello World
Durchstarten mit Scala
19
Herunterladen