Scala ist eine funktionale und objektorientierte Programmiersprache.
Konzepte Integration mit Java Scala-Programme können Java Archive (umgangssprachlich JAR-Datei) ansprechen und Java-Programme können Scala-Dateien ansprechen.Englischsprachige Vorstellung von Scala auf deren offizieller Internetseite. Es können also alle bestehenden Java-Bibliotheken und -Frameworks in Scala-Projekte eingebunden und dort genutzt werden. Umgekehrt ist das zwar prinzipiell möglich, in der Praxis aber nicht immer problemlos. Ähnliches gilt für die meisten Werkzeuge: Entwicklungsumgebungen wie Eclipse, NetBeans oder IntelliJ unterstützen Scala ebenfalls.
Objektorientierung Scala ist, anders als Java, eine rein objektorientierte Programmiersprache. Jeder Wert ist ein Objekt. Das gilt auch für primitive Datentypen, ohne dass es zu Performance-Einbußen kommt, denn der vom Compiler erzeugte Bytecode verwendet primitive Datentypen.
Interfaces werden über den Mechanismus der Traits implementiert. Traits bestehen nicht nur aus Definitionen, sie können bereits konkrete Implementierungen von Methoden enthalten. Klassen können einen oder mehrere Traits erweitern (Schlüsselwort extends).Traits und Mixin Class Composition (englische Erläuterung auf der Scala-Internetseite) Dabei handelt es sich nicht um Mehrfachvererbung, sondern um einen Mixin-Mechanismus.
Über das Schlüsselwort object (anstelle von class) wird eine Implementierung des Entwurfsmusters Singleton bereitgestellt. Statische Felder oder Methoden fehlen in Scala, sie werden stattdessen in einem object definiert.
Funktionale Sprache
Funktionen sind First-Class-Objekte. Sie können an allen Stellen verwendet werden, an denen Werte erlaubt sind, z. B. Zuweisung an eine Variable (hier ist nicht das Ergebnis der Funktionsauswertung gemeint, sondern die Funktionen selbst) oder bei der Parameterübergabe. Methoden sind selbst aber keine First-Class-Objekte, können aber jederzeit in Funktionen umgewandelt werden. Auch Funktionen höherer OrdnungFunktion höherer Ordnung (englische Erläuterung auf der Scala-Internetseite) sind in Scala realisiert, wodurch beispielsweise Currying ermöglicht wird.
Pattern Matching Ein wichtiger Aspekt zur Unterstützung der funktionalen Programmierung mit Scala ist Pattern Matching. Im Gegensatz zu der switch-Anweisung, wie sie zum Beispiel in Java implementiert ist, arbeitet Pattern Matching nicht nur auf der Basis von Werten, sondern auch bezogen auf die Struktur bzw. den Typ eines Objektes. Um auf eine Instanz Pattern Matching anwenden zu können, muss es für sie ein Singleton-Objekt geben, das die unapply-MethodeExtractor Objects (englische Erläuterung auf der Scala-Internetseite) implementiert. So kann man Werte aus der Klasse extrahieren, die entsprechend der unapply-Methode von den Feldern der Klasse abgeleitet werden. Da man oft nur die Felder selbst extrahieren muss, gibt es sogenannte Case ClassesCase Classes (englische Erläuterung auf der Scala-Internetseite) in Scala. Bei ihnen generiert der Scala-Compiler automatisch und vom Programmierer unbemerkt ein gleichnamiges Singleton-Objekt mit apply- und unapply-Methode.
Der folgende Code implementiert die Suche in einem binären Suchbaum mithilfe von Pattern Matching und Case Classes:
enum Tree: case Leaf(key: Int) case Branch(key: Int, left: Tree, right: Tree)
import Tree._
def contains(tree: Tree, key: Int): Boolean = tree match case Leaf(i) => i == key case Branch(i, _, _) if i == key => true case Branch(i, left, _) if i > key => contains(left, key) case Branch(i, _, right) => contains(right, key)
Beispielaufruf: val sorted: Tree = Branch(4, Leaf(2), Branch(7, Leaf(6), Leaf(8)))
println(s"contains(sorted, 4) -> ${contains(sorted, 4)}") println(s"contains(sorted, 5) -> ${contains(sorted, 5)}") println(s"contains(sorted, 6) -> ${contains(sorted, 6)}")
// Ausgabe: // contains(sorted, 4) -> true // contains(sorted, 5) -> false // contains(sorted, 6) -> true
Closures
Funktionen greifen nicht nur auf ihre Parameter und lokalen Variablen zu, sondern auch auf Variablen ihres Kontextes (Scope), welche zum Auswertungszeitpunkt gültig sind. Dadurch werden aus open terms die namensgebenden closed terms. Falls sich bei mehrfacher Verwendung der Funktion der Wert einer Variablen des Kontextes gegenüber einem früheren Auswertungszeitpunkt ändert, kann sich auch der Rückgabewert und das Verhalten der Funktion ändern.
Typsystem Scala ist statisch typisiert. Generische Klassen verwenden Typen, die zum Entwicklungszeitpunkt noch nicht festgelegt sind, z. B. List[T] Oberklassen können abstrakte TypenAbstrakte Typen (englische Erläuterung auf der Scala-Internetseite) vorgeben, die von deren Unterklassen in Form konkreter Typen spezifiziert werden müssen. Gleiches gilt für Variablen (var und val) sowie Methoden.
Kovarianz und Kontravarianz
Typparameter einer generischen Klasse können mit einer Annotation versehen werden, die bestimmt, wie sich Untertyprelationen von Typargumenten auf die Untertyprelation von generischen Instanziierungen der Klasse auswirken. Invarianz, Syntax: K[T], bedeutet, dass überhaupt kein Zusammenhang besteht. Kovarianz, Syntax: K[+T], bedeutet, dass sich die Relation in gleicher Richtung fortsetzt: Wenn T Untertyp von U ist, dann ist K[T] Untertyp von K[U]. Kontravarianz, Syntax: K[-T], bedeutet, dass die Fortsetzung in der Gegenrichtung erfolgt: Wenn T Untertyp von U ist, dann ist K[U] Untertyp von K[T]. Varianzannotationen beeinflussen, an welcher Stelle innerhalb der generischen Klasse der Typparameter benutzt werden darf: Kovariante Typparameter dürfen beispielsweise nicht als Typ von Methodenargumenten verwendet werden, kontravariante nicht als Rückgabetyp.Rückgabetyp (englische Erläuterung auf der Scala-Internetseite)
Typinferenz Typinferenz ist die Fähigkeit des Compilers, den Typ eines Ausdrucks aus dem Kontext herzuleiten, welche unter Syntax beispielhaft dargestellt ist.
Auswertungsstrategie Funktionale Ausdrücke werden in Scala strikt ausgewertet. Allerdings kann durch das Schlüsselwort lazy die verzögerte Auswertung (Lazy-Evaluation) von einzelnen Ausdrücken spezifiziert werden. Auch die Collection-Klassen unterstützen mit den Methoden view und force die Möglichkeit der verzögerten Auswertung. Im Gegensatz hierzu werden in Haskell die Programme standardmäßig lazy ausgewertet und es existieren Strictness-Annotationen.
XML Scala 2 unterstützt XML mittels Syntax (XML-Literale) und Standardbibliothek. Mit Version 2.13 wurde die XML-Unterstützung aus der Standardbibliothek entfernt und in das eigenständige Projekt scala-xml ausgelagert. Mit Scala 3 wurde auch die Unterstützung für XML-Literale deprecated und wird somit künftig entfernt.
In der scala-xml-Bibliothek sind grundlegende XML-Operationen und -Datentypen verfügbar: XML-Konstruktoren, -Serialisierung und -Deserialisierung, XPath-ähnliche Extraktion von Elementen und Attributen: val liste = <einkaufsliste> <artikel><name>Brot</name><kosten>3.50</kosten></artikel> <artikel><name>Apfel</name><kosten>0.29</kosten></artikel> <artikel><name>Eier</name><kosten>1.19</kosten></artikel> </einkaufsliste> val gesamtkosten = (liste \\ "kosten").map(_.text.toDouble).sum // Ergebnis: 4.98
Implicits Methoden können mittels des Modifiers implicit zu sogenannten implicit methods werden. Wenn der Compiler ein Objekt eines bestimmten Typs A erwartet, aber ein Objekt des inkompatiblen Typs B vorfindet, sucht er im lexikalischen Scope und im companion object von A nach einer implicit method, mit der er das B-Objekt in ein A-Objekt konvertieren kann. Mit dieser Technik lassen sich die aus C# bekannten extension methods nachbilden (das sogenannte pimp my library-Pattern) und in Grenzen sogar Vererbung.
Die letzte Parameterliste einer Methode kann ebenfalls als implicit markiert werden. Wenn die Parameterliste beim Aufruf einer Methode fehlt, aber ein als implicit markierter Wert im lexikalischen Scope zu finden ist, wird er automatisch an die Methode übergeben. Hiermit ist es möglich, die aus Haskell bekannten type classes als Entwurfsmuster nachzubildenType classes (PDF). Scalaz, eine Library zur pur funktionalen Programmierung in Scala, setzt Typklassen weiträumig ein.
Mit impliziten Konversionen wird in Scala auch Verhalten in Bibliotheken implementiert, das viele Sprachen als Spezialfall im Compiler abdecken. So zum Beispiel besondere Regeln beim Zusammenfügen von Zeichenketten wie 42 + "etwas Text" oder die Konvertierung von Zahltypen mit kleinerem Wertebereich zu Zahltypen mit größerem Wertebereich, in Java String concatenation operator + bzw. widening primitive conversions in der Spezifikation der Java-Sprache genannt.
Nebenläufigkeit
Während Scala Threads durch die Java-Klassenbibliothek unterstützt, gibt es in Scalas eigener Bibliothek eine Implementierung von Aktoren. Diese wurde von der Aktoren-Implementierung, wie sie in Erlang umgesetzt wurde, inspiriert. Seit der Scala-Version 2.11 ist die ursprüngliche Aktoren-Implementierung nicht mehr Bestandteil der Standardbibliothek. Ersetzt wird sie durch die Akka-Implementierung (ab der Version 2.10 verfügbar).
Zusätzlich implementiert die Standard-Library von Scala Futures und parallele Collections.
Syntax Die Syntax der Sprache ist an Java und ML angelehnt. Von Java wurde vor allem eine Reihe von Schlüsselworten sowie die Blocksyntax übernommen, von ML die Syntax für Typannotationen und Deklarationen.
Im Vergleich zur Java-Syntax kann in den meisten Fällen das Semikolon am Ende einer Zeile entfallen. Die Syntax zur Typdefinition von Variablen und Rückgabewerten lehnt sich an der von ML statt von Java an: Man formuliert nicht Typ variable, sondern variable: Typ.
Die Deklaration und Definition von Werten, Variablen und Methoden erfolgt mittels der Schlüsselwörter val, var und def, gefolgt von Typangaben. val wert: Int = 42 var variable: Double = 3.14 def methode(parameter1: String, parameter2: Boolean): Unit Der Compiler leitet den Typ einer Variable aus dem Kontext ab (Typinferenz). Die beiden Zeilen var x = "Ein Text" und var x: String = "Ein Text" sind somit gleichwertig.
Klassen- und Methodennamen können einen großen Umfang von Zeichen und Symbolen nutzen. Es sind z. B. Bezeichner wie +, *, ::, \\ oder isEmpty_? erlaubt.
Methodenaufrufe mit keinem oder einem Parameter können unter Auslassung des Punktes und der öffnenden und schließenden runden Klammern notiert werden (ähnlich wie in Smalltalk oder Objective-C): 5.0 + 2.0 "Test" startsWith "T" List(1,2,3) isEmpty entspricht 5.0.+(2.0) "Test".startsWith("T") List(1, 2, 3).isEmpty
Mit Scala ist es außerdem möglich, den Quelltext im Vergleich zu Java in vielen Fällen kompakter zu schreiben, zum Beispiel auf Grund von Typinferenz, for comprehensions oder anonymen Funktionen.
Operatoren Für Präfix-Operatoren gibt es eine fest vorgegebene Menge, nämlich +,-,~ und !. Der Ausdruck -x bedeutet das gleiche wie x.unary_-
Postfix-Operator-Ausdrücke sind ebenfalls möglich. Hier gibt es keine Einschränkungen an den Operator, und das Übersetzungsergebnis ist ein Aufruf der (parameterlosen) Methode auf dem Operanden.
Bei Infix-Operatoren entscheidet das erste Zeichen des Operatornamens über Präzedenz und Assoziativität, das den aus der Mathematik üblichen Konventionen folgt. Das Codefragment 1 + z * x wird übersetzt zu (1).+(z.*(x)) Auf dem Objekt 1 wird die Methode + aufgerufen und dabei der Parameter z.*(x), also das Ergebnis eines weiteren Methodenaufrufes, übergeben.
Endet der Methodenname eines Infixoperators mit einem Doppelpunkt, so vertauscht sich die Reihenfolge von Empfänger und Parameter und der Operator ist rechtsassoziativ: a :: b wird übersetzt zu b.::(a)
Schleifen For-Schleifen wurden zu sogenannten for comprehensions soweit generalisiert, dass sie nicht nur mehrere verschachtelte Schleifen zusammenfassen, sondern analog zu Haskells Do-Notation beliebige Monaden nutzen können.