Artikel

 
Juli 2009 | Artikel

Scala kann auch funktionale Programmierung

(Link zum Artikel: http://www.it-republik.de/jaxenter/artikel/2439)

Teil 3 der großen Scala-Serie

Text: Arno Haase
  • Teilen
  • kommentieren
  • empfehlen
  • Bookmark and Share
Scala ist eine ziemlich coole, ausgereifte Sprache für die JVM. Sie ist funktional, objektorientiert und darauf angelegt, dass man mit ihr Bibliotheken schreiben kann, die sich wie Spracherweiterungen anfühlen. Nachdem wir im zweiten Teil die objektorientierte Seite von Scala näher angesehen haben, wenden wir uns jetzt den Methoden und Funktionen zu.
Teil 1   Teil 2   Teil 3   

Zuerst ein paar Worte zum Thema Performance: Das Leser-Feedback zum ersten Teil der Serie drehte sich schwerpunktmäßig um diesen Punkt, und Performance ist ja auch wichtig. Die kurze Antwort ist: Scala-Code ist im Wesentlichen genauso schnell wie Java-Code. Und – um auch gleich auf den größeren Kontext der Performancefrage einzugehen – Scala-Code hat zur Laufzeit auch die ganzen nichtfunktionalen Eigenschaften wie Robustheit, Sicherheit, Portierbarkeit etc. von Java-Code (siehe Kasten "Performance").

Performance

Es gibt eine Reihe von Benchmarks, die die Performance von Scala mit Java und teilweise anderen Sprachen vergleichen (z.B. hier, hier, hier). Solche Messungen sind immer mit einer gewissen Vorsicht zu genießen, weil gute Performancevergleiche schwierig sind und jede Sprache für manche Anwendungsfälle besser, für andere schlechter geeignet ist.

Es sieht aber durchweg so aus, dass Scala etwa so schnell ist wie Java – also (inzwischen) sehr schnell. Das sollte auch keine große Überraschung sein, denn der Scala-Compiler erzeugt im Wesentlichen denselben Bytecode wie Java. Beide Sprachen sind statisch getypt und haben kein Metaobjektprotokoll – oder anders gesagt, Scala orientiert sich in performancekritischen Punkten sehr weitgehend an Java. Es gibt eine Reihe von Stellen, wo Scala auf den ersten Blick erheblich mehr Overhead hat als Java, und die sind eine Erklärung wert.

  • Arrays und Primitives: In Scala ist alles ein Objekt, und Arrays, Ints etc. sind durch "richtige" Klassen repräsentiert. Das suggeriert einen Overhead, aber der Schein trügt: Der Scala-Compiler erzeugt Bytecode, der mit den "echten" Java Primitives arbeitet. Die Convenience hat also keinen Preis zur Laufzeit.
  • Tail-Rekursion: Rekursive Aufrufe sind in Java teurer als Schleifen, weil ja jeweils die Parameter auf dem Stack abgelegt werden müssen. Der Scala-Compiler optimiert allerdings einen rekursiven Aufruf, der als letzter Ausdruck in einer Funktion ausgeführt wird, zu einem Sprung an den Anfang – und erzeugt damit denselben Bytecode wie der Java-Compiler für eine Schleife. Rekursion muss also nicht teuer sein.
  • Traits sind Interfaces: Traits, das Scala-Konstrukt für Implementierungs-Mehrfachvererbung, arbeiten intern mit Delegation und erzeugen dadurch einen gewissen marginalen Overhead. Es gibt in Java andererseits kein entsprechendes Konstrukt, und Traits ohne Zustand und Code werden vom Scala-Compiler in völlig normale Java-Interfaces umgesetzt.

Scala ist also definitiv schnell. Der Compiler erzeugt Bytecode, der dem des Java-Compilers so ähnlich wie möglich ist, und die Laufzeitumgebung ist ja ohnehin dieselbe. Der funktionale Programmierstil hat allerdings an einigen Stellen Auswirkungen auf die Performance. So ist in Scala, wie in vielen anderen funktionalen Sprachen, eine einfach verkette Liste der "Default"-Listentyp. Das Hinzufügen eines Elements am Anfang ist damit sehr billig, dafür kostet der wahlfreie Zugriff mehr.

Hinter der Frage der Performance steckt aber oft die allgemeinere Frage, ob Scala "reif", "solide" eben "echt einsetzbar" ist. Deshalb auch ein paar Worte dazu – David Pollak schreibt in seinem Blog ausführlicher darüber. Scala ist in technischer Hinsicht definitiv ausgereift und robust genug für den produktiven Einsatz. Es gibt guten Support und eine lebendige Community. Die Produktivität mit Scala ist deutlich besser, und der Lernaufwand für Java-Entwickler, die ein wenig über ihren Tellerrand schauen, ist sehr überschaubar – eher geringer als für Groovy.

Besondere Methoden

Als Beispiel für die Ausdrucksmöglichkeiten von Methoden und Funktionen schreiben wir eine Klasse, die für Personen das dazugehörige Alter verwaltet:

  1. import scala.collection.mutable._
  2. class Alter {
  3. private val alterMap = new HashMap[String, Int] ()
  4. def update (name: String, alter: Int) =
  5. alterMap += (name -> alter)
  6. def apply (name: String) =
  7. "Das Alter von " + name + " ist " + alterMap(name) + "!"
  8. def enthält (name: String) = alterMap.contains (name)
  9. def += (o: Tuple2[String, Int]) = update (o._1, o._2)
  10. }

Die erste Zeile importiert die Klasse HashMap (in der veränderlichen, also mutable-Variante). Die Klasse selbst hält die Daten in einer solchen HashMap, die zum Namen einer Person das Alter als Int speichert. Und weil die Map selbst veränderlich ist, können wir sie als val (statt var) definieren – die Referenz auf die Map ändert sich ja nie. Die Klasse definiert vier Methoden. Die erste von ihnen, update, registriert eine Person mit Alter, während die zweite, apply, einen erklärenden Text zum Alter einer Person zurückliefert. Die dritte Methode, enthält, fragt ab, ob für eine bestimmte Person ein Alter registriert wurde. So weit sehen die Methodendefinitionen noch völlig normal aus. Die vierte Methode hat den bemerkenswerten Namen "+=". Sie nimmt einen Namen und das dazugehörige Alter als 2-Tupel entgegen und ruft mit diesen Daten update auf. Scala macht sehr wenige Einschränkungen auf Methodennamen, und += ist für Scala ein Name wie jeder andere auch. Operator Overloading ist also kein Sonderfall mit Spezialsyntax, sondern Operatoren sind ganz normale Methoden mit einem etwas ungewöhnlichen Namen. Spannend wird es, wenn wir jetzt unsere Klasse verwenden:

  1. val alter = new Alter()
  2. alter ("Fred") = 10
  3. alter ("Georg") = 12
  4. alter += ("Friederike", 11)
  5. println (alter ("Fred"))
  6. println (alter enthält "Georg")
  7. println (alter.enthält ("Georg"))

Die ersten beiden Zeilen nach dem Erzeugen des Alter-Objekts registrieren offenbar das Alter für Fred und Georg. Die Syntax ist intuitiv lesbar – aber was passiert da bitteschön unter der Oberfläche!? Die Antwort liegt darin, dass in Scala die Methode update diese Zuweisungssyntax unterstützt. Oder anders gesagt: Wenn man auf irgendeinem Objekt Parameter in Klammern schreibt und dem Ganzen einen Wert zuweist, macht der Compiler daraus einen Aufruf der entsprechenden update-Methode.

Die Zeile danach registriert die elfjährige Friederike mithilfe der +=-Methode. Sie ist eine Methode wie jede andere auch, aber in Scala kann man bei jeder Methode mit genau einem Parameter den Punkt und die Klammern des Aufrufs weglassen. Das Ganze nennt sich Infix-Notation und ist für Operatoren eine besonders natürliche Schreibweise. Um den Namen und das Alter steht eine Klammer, die aber nichts mit dem Methodenaufruf zu tun hat. Sie ist einfach die Scala-Notation für ein Tupel, also zwei Werte, die zu einem zusammengefasst werden.

Jetzt kommen wir zu den Abfragemethoden. Der Ausdruck alter ("Fred") liefert offenbar das Alter, das zu Fred gehört. Er ist eine besondere Syntax, die Scala auf einen Aufruf der Methode apply abbildet. Oder anders gesagt: Wenn man hinter einem Objekt Werte in Klammern schreibt, macht der Scala-Compiler daraus immer einen Aufruf der entsprechenden apply-Methode. Und schließlich haben wir noch einen Aufruf, der die Methode enthält, einmal in Infix-Notation und einmal als normalen Methodenaufruf. Es zeigt, dass beide Notationen möglich sind, dass aber die Infix-Schreibweise oft besser lesbar ist.

Die Methoden apply und update werden übrigens ziemlich ausgiebig von den Scala-Bibliotheken verwendet. So ist z.B. die Factory-Methode List (1, 3, 99) oder der Zugriff myList (2) auf diese Weise implementiert. So kann man Klassen schreiben, die natürlich in der Verwendung sind, ohne dass sie deshalb zum Sprachkern gehören müssen.

Teil 1   Teil 2   Teil 3   

andere Artikel dieser Serie


Anzeige

Kommentare


Anzeige

zurück zum Seitenanfang