Ein zweiter Blick auf JVM-Programmiersprachen

Seite 2: Scala

Inhaltsverzeichnis

Scala ist – wie Groovy – im Jahr 2003 entstanden. Ein Team um Martin Odersky wollte eine Programmiersprache schaffen, die statisch typisiert ist und objektorientierte mit funktionalen Paradigmen vereint. Im folgenden Codebeispiel ist die statische Typisierung bei dem Methodenparameter ersichtlich, allerdings kommt hier die Postfix-Notation zum Einsatz, das heißt, der Typ steht nach dem Variablenbezeichner.

def sayHello(name: String) {
println(s"Hallo $name!")
}

sayHello("JavaLand")

In Scala gibt es die Konvention, dass Klammern bei Funktionsaufrufen mit Seiteneffekten stets mit angegeben werden, damit deutlich ist, dass es sich nicht um einen Operator handelt. Der Nebeneffekt von sayHello ist die Ausgabe auf der Konsole.

Obwohl Scala statisch typisiert ist, lässt sich die Typangabe in vielen Fällen weglassen. Der Compiler bestimmt dann den passenden Typ (Typinferenz). Im nächsten Listing weiß er etwa, dass setB vom Typ Set[Int] ist und die Typangabe kann entfallen. Bei doubleB ist klar, dass ein Int zurückgegeben wird. Bei Methodenparametern wird der Typ nicht inferiert und ist immer explizit anzugeben.

val setA: Set[Int] = Set(1, 2, 3)
val setB = Set(1, 2, 3)

def doubleA(i: Int): Int = i * 2
def doubleB(i: Int) = i * 2

Ein mächtiges Sprachkonstrukt in Scala sind die For-Ausdrücke. Wie im nächsten Code-Beispiel gezeigt, lassen sie sich zum Iterieren (von 1 bis 10) und Filtern (alle graden Zahlen) verwenden. Wurde ein passendes Element gefunden, kann das Programm es mit yield zurückgeben, sodass sich am Ende eine neue Collection mit allen passenden Elementen erzeugen lässt.

val even = for {
i <- 1 to 10
if i % 2 == 0
} yield i

assert(List(2, 4, 6, 8, 10) == even)

Pattern Matching ist die generalisierte Form des bekannten switch-Statements, allerdings ohne Fall-through. Da sich ganze Objektgraphen matchen lassen, eröffnen sich vielfältige Möglichkeiten. Im unten stehenden Beispiel ist eine Konferenz mit einem bestimmten Namen zu suchen. Dafür wird eine case-Klasse erstellt, die sich in einer Fallunterscheidung verwenden lässt, da der Scala-Compiler sie entsprechend aufbereitet.

case class Conference(name: String)

val javaLand = Conference("JavaLand")

javaLand match {
case Conference("JavaLand") =>
println("JavaLand!")
case _ =>
println("Default Case")
}

Scala bietet mit Traits die Möglichkeit, einzelne Aspekte auszulagern und in Klassen unterzubringen (sogenannte Mix-ins). Dieses Vorgehen stellt eine Form der Mehrfachvererbung dar, die allerdings durch Delegation realisiert wird und so das Diamantenproblem geschickt umgeht. Dabei werden die super-Aufrufe dynamisch gebunden und in eine lineare Abfolge gebracht. Die Bindung von super ist so zur Laufzeit eindeutig (nämlich immer an den nächsten Trait in der Hierarchie). Dank der Linearisierung lassen sich Mehrdeutigkeiten vermeiden und die Vorteile der Mehrfachvererbung, wie die feingranulare Aufteilung in Aspekte, nutzen. Im nächsten Code-Auszug werden die Merkmale "schwimmen" und "fliegen" in Traits ausgelagert und lassen sich dann einmischen. Im Beispiel ist ein Pinguin ein Vogel, der um den Aspekt "schwimmen" erweitert wird, ein Adler hingegen ist ein fliegender Vogel.

trait Schwimmen { def schwimme() {} }

trait Fliegen { def fliege() {} }

abstract class Vogel

class Pinguin extends Vogel with Schwimmen
class Adler extends Vogel with Fliegen

Die Kombination von objektorientierter und funktionaler Programmierung ist in Scala besonders ausgeprägt. Die funktionale Herangehensweise ermöglicht oft eleganten und einfachen Code und durch den Verzicht auf Seiteneffekte lässt sich der Code gut parallel ausführen. Das ausgefeilte Typsystem und die Vielzahl an Bibliotheken und Frameworks machen Scala zu einer empfehlenswerten Programmiersprache. An Scala gibt es allerdings auch Kritik, so werden stellenweise die Geschwindigkeit des Compilers und die Abwärtskompabilität bemängelt. Diese Probleme gehen die Scala-Entwickler allerdings aktiv an.