Stand: 2019-06-02

Kotlin vs. Scala

Was spricht für und gegen Kotlin und Scala für größere und langlaufende Software-Projekte?

Inhalt

Scala und Kotlin ähneln sich auf den ersten Blick – auf den zweiten unterscheiden sie sich sehr. Statt Funktionalität für Funktionalität zu vergleichen geht es hier eher um Qualitätseigenschaften wie Erlernbarkeit, Werkzeugunterstützung oder Zukunftssicherheit.

Ursprung und Philosophie

Scala hat einen akademischen Ursprung und ist an der École polytechnique fédérale de Lausanne (EPFL) in der Schweiz entstanden. 2004 wurde die erste Version veröffentlicht. Bekannter wurde Scala aber erst um das Jahr 2008 herum. Ziel war die Vereinigung objektorientierter und funktionaler Programmierung in einer statisch typisierten Programmiersprache.

Mehr zu Scalas Ursprung steht in dem Interview “The Origins of Scala” mit Sprachschöpfer Martin Odersky.

Kotlin stammt von dem tschechischen Hersteller für Entwicklungswerkzeuge JetBrains. Im Jahr 2011 hat die Sprache das Licht der Welt erblickt. Damals suchte JetBrains nach einer ausdrucksstarken Sprache, um die Menge an Code in den eigenen Produkten zu reduzieren. Diese Sprache sollte die Produktivität und Qualität steigern, für gute Werkzeugunterstützung geeignet sein, so schnell wie Java ausgeführt werden, schnell kompiliert werden können, einfach erlernbar sein und nahtlos mit Java zusammenspielen.

To summarize, we were looking for a language that could cut down our code base (IntelliJ platform and server side tools are written in Java), be concise but still expressive, toolable, as fast as Java, easy ramp-up time, and very important, interoperable with all the existing code base we had.
Given the candidates at the time, it was decided to start Kotlin.

Hadi Hariri, JetBrains, 25. Juli 2015

Weitere Einzelheiten, warum JetBrains Kotlin ins Leben rief, liefert der Artikel “Why JetBrains needs Kotlin”.

Jede der beiden Sprachen ist bis heute von ihrem Ursprung geprägt: Scala ist eher auf die Lösung akademisch interessanter Probleme ausgerichtet, während Kotlin eher auf die Bedürfnisse von kommerziellen Software-Projekten ausgelegt ist. Das sind aber nur Tendenzen und bedeutet natürlich nicht, dass man mit Scala keine kommerziellen Anwendungen entwickeln könnte; genauso wenig schließt es aus, dass man sich in Kotlin akademischen Herausforderungen widmen kann.

Erlernbarkeit

Wenn man schon eine Sprache wie Java oder C# kennt, kann man mit Kotlin innerhalb von ein paar Tagen produktiv werden. Das habe ich erst kürzlich wieder in einem Projekt erlebt. Das Team war von Tag 1 an arbeitsfähig und hat jeden Tag nebenbei seine Kotlin-Kenntnisse ausgebaut. Nach ein paar Wochen war die Programmiersprache kein Thema mehr.

Ein ähnliches Szenario hatte ich vor ein paar Jahren mit Scala und auch dort sind wir eigentlich ganz gut zurechtgekommen. Allerdings haben wir Scala eher als besseres Java eingesetzt. Tatsächlich ist Scala eine sehr “tiefe” Sprache, mit der man sich ewig beschäftigen kann.

Jedes zusätzliche Sprachmerkmal ist zugleich ein Kompromiss, weil es Lernaufwand verursacht, falsch angewendet werden kann und zu einer Vielfalt an Lösungswegen und Syntaxvarianten führen kann.

Ein grundsätzliches Problem in vielen Teams ist das unterschiedliche Qualifikationsniveau der Entwickler. Kotlin ist dafür viel besser als Scala geeignet, weil Entwickler nicht so viele Konzepte lernen und im Code erkennen müssen. Um neue Entwickler nicht mit der Vielfalt der Konzepte zu überwältigen, wurden Scala Levels vorgeschlagen. Sprachkonzepte sollten in 6 Stufen gelernt und genutzt werden. Eine Idee dabei war, dass reine Nutzer nicht so viel können müssen, wie API-Designer. Allerdings ist davon nicht mehr viel zu hören und vermutlich war dieser Ansatz allein schon deshalb zum Scheitern verurteilt, weil man sehr schnell auch Code verstehen muss, der über das eigene Level hinaus geht – zum Beispiel Code von erfahreneren Kollegen oder aus Frameworks.

Eine Unterscheidung zwischen API-Designern und normalen Programmierern soll es in Kotlin prinzipiell nicht geben:

Another thing is that we think that all members of a team should be equal in terms of language features that they can use, so the set of features we have is as good for library writers as for application writers. One can call this a “democratic” approach to language design.

Andrey Breslav, Kotlin Lead Language Designer, 2016-02-18

Das Technologieberatungsunternehmen ToughtWorks hat Scala im Jahr 2014 zwar die beste Bewertung, ADOPT, gegeben, gleichzeitig aber eingeschränkt, dass das nicht für die gesamte Sprache gelten würde sondern jedes Unternehmen selbst “Scala, the good parts” finden müsste.

Scala hat oft nicht nur konzeptionell mehrere konkurrierende Wege zum Ziel, sondern auch noch mehrere Syntaxvarianten für einen Weg. Alle folgenden Ausdrücke sind äquivalent:

list.map(_ + 1)
list map(_ + 1)
list.map(x => x + 1)
list.map((x) => x + 1)
list.map { _ + 1 }
list map { _ + 1 }
list.map { x => x + 1 }
list.map { (x) => x + 1 }
list.map(1 +)

Entwickler müssen unnötig viele Syntaxvarianten lernen und im Code erkennen.

Dagegen werden in Kotlin für Lambdaausdrücke immer geschweifte Klammern verwendet. Obige Varianten reduzieren sich dadurch auf zwei, je nachdem ob man den Parameter benennen will oder nicht:

list.map { it + 1 }
list.map { x -> x + 1 }

Letztlich ist die Programmiersprache nur ein Mittel zum Zweck und man sollte nich zu viel über die Sprache selbst nachdenken müssen. Aus genau diesem Grund verwendet Googles Framework Flutter die Programmiersprache Dart. “Why Flutter Uses Dart":

The hard part about learning a new system is typically not learning the language, it is learning all the libraries, frameworks, tools, patterns, and best practices for writing good code.

Wm Leler; Developer Advocate for Flutter and Dart at Google; 2018-02-26

Mir ist aufgefallen, dass ich in Kotlin viel weniger als in Scala über die Sprache selbst oder den schönsten und elegantesten Weg nachdenke, sondern mich mehr auf die Lösung des eigentlichen Problems (nämlich das des Kunden) konzentriere.

Konsistenz

Scala ist an ein paar Stellen konsistenter als Kotlin, was prinzipiell auch der Erlernbarkeit zugute kommt.

Zum Beispiel nutzen catch-Blöcke das allgemeine Pattern Matching. Das hat nicht nur den Vorteil, dass man nur eine Syntax lernen muss, sondern bietet auch erweiterte Möglichkeiten, wie etwa die Prüfung auf eine bestimmte Nachrichten oder innere Exceptions (cause).

In Scala dient der letzter Ausdruck eines Blocks immer als Rückgabewert, ein explizites return ist nicht erforderlich.

def asWord(number: Int): String = {
    val w = if (number == 1) "eins" else "zwei"
    w.toUpperCase() // ohne return
}

List(1, 2).map { n => 
    val w = if (n == 1) "eins" else "zwei"
    w.toUpperCase() // ohne return
}

In Kotlin ist dagegen return bei Funktionsrümpfen mit mehreren Ausdrücken erforderlich, während es in einer anonymen Funktion nicht nötig ist:

fun asWord(number: Int): String {
    val w = if (number == 1) "eins" else "zwei"
    return w.toUpperCase() // mit return
}

listOf(1, 2).map {
    val w = if (it == 1) "eins" else "zwei"
    w.toUpperCase() // ohne return
}

Ein kleiner Unterschied ist auch noch die Verwendung von = bei Funktionen, denn in Scala wird es immer geschrieben, wohingegen es bei Kotlin nur geschrieben wird, wenn der Rumpf aus genau einem Ausdruck besteht.

Sowohl Kotlin als auch Scala erlauben symbolische Methodennamen wie +, was auch als Operatorüberladung bezeichnet wird. In Kotlin sind nur wenige gängige Symbole wie +, - oder * erlaubt. Definiert werden solche Operatorfunktionen mit dem Schlüsselwort operator und einem der festgelegten Namen, zum Beispiel so:

operator fun plus(other: Centimeter): Centimeter = Centimeter(value + other.value)

In Scala sind Operatoren nichts besonderes, es sind lediglich Methoden mit symbolischen Namen:

def +(other: Centimeter) = new Centimeter(value + other.value)

Aber auch wenn das auf den ersten Blick sehr einfach und konsistent ist, gibt es doch ein paar Regeln, die etwa die Reihenfolge bestimmter Operatoren festlegen, um Punktrechnung vor Strichrechnung zu ermöglichen.

Ungewöhnlich ist, dass es auch eine Bindung von rechts nach links gibt, aber nur wenn der Operator mit einem Doppelpunkt endet. Das folgende Beispiel macht es anschaulich:

scala> case class A(value: String) {
     |   def ::(other: A) = value + other.value
     | }
defined class A

scala> A("a") :: A("b")
res1: String = ba

Wie man sieht, wird hier nicht a mit b verkettet, sondern b mit a. Wer sich jetzt fragt, wozu das gut sein soll, findet die Erklärung in verketteten Listen, denn die kann man damit so konstruieren:

1 :: 2 :: Nil

Meiner unmaßgeblichen Meinung nach ist das allerdings ein ziemlich überflüssiges Sprachmerkmal, das weder den Implementierungs- noch Lernaufwand rechtfertigt.

Der größte Nachteil der frei definierbaren, symbolischen Methodennamen in Scala ist aber, dass diese Freiheit nur allzu gerne missbraucht wird. Ich fürchte, das hier meinen die Entwickler ernst:

((circleView <~~ move(rippleBackground)) ~~ 
   (rippleBackground <~~ ripple(rippleData)) ~~ 
      (circleView <~~ fadeIn(1000))

Was soll <~~ bedeuten?! Und was ist der Unterschied zu ~~?

Das ist zum Glück nicht der Normalfall in Scala, aber symbolische Methoden begegnen einem trotzdem öfter, als es sinnvoll wäre, wie Akka HTTP zeigt, das die Methode ~ zur Aneinanderreihung von Routen verwendet. Kotlin setzt solchem Wildwuchs gesunde Grenzen.

Properties, die von außen wie ein gewöhnliches Feld aussehen, und die Logik zum Lesen und Schreiben des Wertes enthalten können, gibt es sowohl in Kotlin als auch in Scala. Auch hier bleibt sich Scala treu und bildet das mit gewöhnlichen Methoden (und einer Namenskonvention) ab:

class Something {
    private var _x = 0.0
    def x = _x * 2.0
    def x_=(value: Double): Unit = _x = value / 2.0
}

Die Syntax für Properties in Kotlin fällt dagegen etwas aus dem Rahmen, denn der Bezug wird nur über Folge der Zeilen, aber nicht wie sonst durch einen Block hergestellt:

class Something {
    var x: Double = 0.0
        get() = field * 2.0
        set(value: Double) {
            field = value / 2.0
        }
}

Immerhin muss man in Kotlin kein Backing Field selbst anlegen, denn das macht der Compiler und stellt es als field bereit.

Verständlichkeit

Wie leicht kann man Kotlin- und Scala-Code lesen und verstehen? Ist er selbsterklärend? Gut geschriebener Code ist in beiden Sprachen auch gut lesbar. Allerdings wird die Verständlichkeit in Scala durch ein paar Sprachmerkmale stark gefährdet.

Dazu gehören zum Beispiel implizite Konvertierungen. Ein Objekt eines bestimmten Typs kann durch eine spezielle Funktion oder eine spezielle Klasse in einen anderen Typ umwandelt werden, ohne dass man dafür an der jeweiligen Stelle explizit die Konvertierung veranlassen müsste. Damit können zum Beispiel Java-Listen in Scala-Listen übersetzt werden und umgekehrt. So kann man sich eine Menge Code sparen und es werden APIs möglich, die anders praktisch undenkbar wären. Aber man sieht im Code eben leider auch nicht mehr auf den ersten Blick was passiert.

Die Schöpfer von Kotlin haben sich entschieden, den Weg von C# zu gehen und Erweiterungsfunktionen zu verwenden. Sie ähneln statischen Methoden, die eine Instanz der erweiterteten Klasse übergeben bekommen, sodass der Aufruf wie der einer normalen Methode aussieht. Beispiel:

fun String.shorten(length: Int) =
    if (this.length <= length) this
    else this.substring(0, length - 4) + "..."

val s = "Es war einmal eine kleine, süße Dirne."
print(s.shorten(20)) 

Es sieht so aus, als wäre shorten eine Funktion der Klasse String, tatsächlich ist es aber eine Erweiterungsfunktion.

In Scala schreibt man für den gleichen Zweck eine Klasse, in die ein String automatisch konvertiert wird, wenn man darauf shorten aufruft:

implicit class StringShortener(value: String) {
    def shorten(length: Int) = 
        if (value.length <= length) value 
        else value.substring(0, length - 4) + "..."
}

Der Unterschied sieht auf den ersten Blick vielleicht nicht so groß aus, aber er ist gewaltig! Denn während beim Aufruf einer Erweiterungsfunktion ziemlich klar ist, wo sie herkommt, ist das bei impliziten Konvertierungen oft nicht offensichtlich. Noch dazu kann es potenziell für ein Objekt mehrere implizite Konvertierungen geben, sodass manchmal sehr schwer nachzuvollziehen ist, was der Code eigentlich macht.

Einen ähnlichen Einfluss auf die Verständlichkeit haben implizite Parameterlisten in Scala. Das sind Parameterlisten, die mit Werten aus dem jeweiligen Kontext belegt werden. Man könnte damit zum Beispiel eine Methode findById mit einem Parameter aufrufen, die tatsächlich aber noch einen zweiten Parameter, nämlich einen EntityManager, bekommt:

repository.findById(id)

Dieser Aufruf entspricht in Wirklichkeit diesem hier:

repository.findById(id)(entityManager)

Voraussetzung dafür ist, dass der EntityManager im Gültigkeitsbereich existiert und mit dem Schlüsselwort implicit markiert ist. Vor ein paar Jahren hatte ich schon mal ein Beispiel dazu geschrieben. So schön und praktisch das auch sein kann, so wenig offensichtlich ist beim Blick auf den Code, was tatsächlich vor sich geht. Man könnte auch sagen, dass Scala hier das “Prinzip der geringsten Überraschung” verletzt.

Noch mehr als mit impliziten Konvertierungen kann man die Sprache durch Makros verbiegen. Noch sind sind Makros zwar “experimentell”, aber sie sind da und irgendwann werden sie wohl regulärer Teil der Sprache.

Methoden mit “by-name parameters” (im Gegensatz zu “by-value”) ermöglichen sehr elegante Syntax, aber sie verschleiern die Tatsache, dass es sich bei dem Parameter um eine anonyme Funktion handelt. Woran soll man im folgenden Beispiel erkennen, dass der zweite Parameter von getOrElse eine Funktion ist?

scala> val m = Map("Italien" -> "Rom", "Frankreich" -> "Paris")
m: scala.collection.immutable.Map[String,String] = Map(Italien -> Rom, Frankreich -> Paris)

scala> m.getOrElse("Deutschland", "?" * 3)
res18: String = ???

scala> m.getOrElse("Italien", "Wird nicht ausgeführt." * 2)
res19: String = Rom

Es hätte genauso gut sein können, dass der Ausdruck sofort evaluiert, aber nicht verwendet wird.

Kotlin macht dagegen offensichtlich, dass eine Funktion übergeben wird:

val m = mapOf("Italien" to "Rom", "Frankreich" to "Paris")
m.getOrElse("Deutschland") { "?".repeat(3) }

An manchen Stellen ist Scala regelrecht kryptisch, wie dieses Beispiel aus der Klasse Beispiel Try zeigt:

flatten[U](implicit ev: <:<[T, Try[U]]): Try[U]

Besonders irritierend ist, dass <:< eine abstrakte Klasse ist.

Ein weiteres schwer verständliches Beispiel stammt aus der Klasse List:

def ++:[B >: A, That](that: collection.Traversable[B])
    (implicit bf: CanBuildFrom[List[A], B, That]): That 

Wenn man ein paar Sprachkonstrukte direkt gegenüberstellt, ist gut erkennbar, wie viel Mühe sich die Macher von Kotlin bei der Verständlichkeit und Lesbarkeit gegeben haben:

Kotlin Scala Anmerkung
for (item in list) for (item <- list) in ist selbsterklärend und gut lesbar. Was der Pfeil bedeutet ist eigentlich nur klar, wenn man andere for-each-Schleifen kennt.
list.map { it + 1 } list.map { _ + 1 } it ist ebenfalls klarer als _. Obendrein sind bei Scala mehrere dieser unbenannten Parameter in einem Lambdaausdruck möglich, was die Lesbarkeit komplett ruinieren kann.
data class case class Bei data ist offensichtlich, dass die Daten im Vordergrund stehen. Was eine case class ist, ist weniger selbsterklärend.
class Foo<in A, out B> class Foo[-A, +B] Letztlich geht es bei Ko- und Kontravarianz darum, ob es sich um Ein- oder Ausgabewerte handelt und genau das drücken in und out sehr gut aus.
fun printAll(
vararg strings: String)
def printAll(
strings: String*)
vararg ist selbsterklärender, dafür ist * etwas konsistenter, da das Sternchem beim Typ steht, der in beiden Fällen verändert wird.
constructor(...) def this(...) In Kotlin werden Konstruktoren mit dem selbsterklärenden Wort constructor bezeichnet. Das dafür verwendete this in Scala ist eher irritierend.
class A {
  companion object { 
    ... 
  }
}
class A { ... }
object A { ... }
"Companion Objects" heißt in Kotlin genau so. In Scala müssen sie wie die zugehörige Klasse heißen und in derselben Datei stehen. Dass das Objekt dadurch zum Companion wird, muss man wissen.

Werkzeugunterstützung

Entwicklungsumgebungen

Da Kotlin, genau wie IntelliJ IDEA, von JetBrains stammt, ist es wenig verwunderlich, dass diese Entwicklungsumgebung Kotlin sehr gut unterstützt. IntelliJ ist meiner Meinung nach auch für Java das beste Werkzeug und die gleiche hohe Qualität bekommt man auch für Kotlin. Der größte Unterschied ist der Umfang der Inspections für die statische Code-Analyse, denn hier bietet IntelliJ für Java deutlich mehr, aber für Kotlin kommen stetig neue hinzu.

Die Unterstützung für Kotlin-Entwickler in IntelliJ ist erstklassig.

Die Kehrseite der Medaille ist, dass man mit Kotlin de-facto auf IntelliJ festgelegt ist. Es gibt zwar auch ein Plugin für Eclipse, aber großen Spaß wird man damit aktuell nicht haben. Das Plugin für NetBeans kann man nach über zwei Jahren ohne neue Version wohl für tot erklären.

Ansonsten unterstützen noch ein paar Editoren wie Visual Studio Code Kotlin, aber nicht auf einem Niveau wie eine ausgewachsene Java-Entwicklungsumgebung.

Für Scala war lange Zeit die auf Eclipse aufbauende Scala IDE das Maß der Dinge. Jedoch sind wichtige Entwickler abhanden gekommen, sodass der Schwung verloren gegangen ist. Die Version 4.7.1 hängt seit November 2017, also zum Zeitpunkt des Schreibens seit eineinhalb Jahren, im “Release Candidate”-Status.

Zustand der Scala IDE (Symbolbild 😉).

Zum Glück gibt es für Scala ein gutes Plugin für IntelliJ von JetBrains. Die Qualität des Kotlin-Plugins erreicht es allerdings nicht. Das könnte zum einen daran liegen, dass JetBrains selbst seine Software in Kotlin schreibt. Aber es könnte zum anderen auch daran liegen, dass Scala nicht so gut für Werkzeugunterstützung geeignet ist. Insbesondere implizite Konvertierungen und implizite Parameter sind Gift für Werkzeuge! Sie erfordern aufwändige Analysen und manchmal interpretiert die Entwicklungsumgebung trotzdem etwas falsch. In IntelliJ gibt es sogar eine Einstellung zur Begrenzung der Suchtiefe für implizite Parameter.

Beschränkung der Suchtiefe für implizite Parameter, um Performance-Problemen vorzubeugen.

Trotz kleiner Schwächen im Detail kann man mit IntelliJ und Scala jedoch sehr gut arbeiten. Jedenfalls macht das Programmieren mit Scala und nicht ganz so guter Werkzeugunterstützung mehr Spaß als mit Java und sehr guter Werkzeugunterstützung.

Außerdem gibt es noch ein Scala-Plugin für Visual Studio Code, das aber weit von dem Funktionsumfang von IntelliJ oder der Scala IDE entfernt ist.

Build Tools

Kotlin-Projekte kann man problemlos mit dem bewährten Maven bauen. Gängiger ist aber Gradle, das nicht nur eine Kotlin-DSL bietet, sondern je nach Anwendungsfall auch noch deutlich schneller ist. Sowohl Maven als auch Gradle unterstützen inkrementelle Übersetzung zur Verkürzung der Build-Zeiten. IntelliJ unterstützt beide Werkzeuge hervorragend.

Auch für Scala können Maven und Gradle verwendet werden. Nur für die Entwicklung von (öffentlichen) Bibliotheken sind diese beiden Werkzeuge weniger geeignet, denn Scala hat die Eigenart, die Binärkompatibilität bei großen Versionswechseln über Bord zu werfen. Eine für Scala 2.10 kompilierte Bibliothek kann deshalb normalerweise nicht von einer auf Scala 2.11 basierenden Anwendung genutzt werden. Wenn man eine Bibliothek für mehrere Scala-Versionen bereitstellen will, ist man mit dem Scala Build Tool, kurz SBT, am besten bedient, das für mehrere Scala-Versionen gleichzeitig bauen kann.

SBT ist zwar in Scala-Projekten weit verbreitet, aber auch umstritten. Mir gefiel SBT schon allein wegen seiner Syntax nie, aber die scheint etwas lesbarer geworden zu sein. Aufgegeben habe ich die Nutzung von SBT, weil die Unterstützung in IntelliJ dermaßen schlecht war, dass SBT unterm Strich mehr behindert als geholfen hat. Wie gut IntelliJ heute mit SBT klar kommt, kann ich nicht sagen, aber der JetBrains-Website zufolge macht sie einen ganz ordentlichen Eindruck. Für große Projekte ist möglicherweise SBT ein Flaschenhals, jedenfalls konnte LinkedIn seine Build-Zeiten nach dem Umstieg von SBT auf Gradle auf bis zu 1/4 reduzieren.

Sowohl für Scala als auch Kotlin gibt es weitere Build Tools. In der Disziplin “Build Tools” gibt es keine allzu großen Unterschiede zwischen beiden Sprachen.

REPL

Um eine Sprache zu lernen oder mal schnell eine Kleinigkeit auszuprobieren, ist es ungemein praktisch, Code in einer Shell interaktiv auszuführen. Aus diesem Grund bieten Scala und Kotlin sogenannte REPLs (Read Evaluate Print Loops) an.

Leider behandelt JetBrains dieses Hilfsmittel stiefmütterlich, sodass Kotlin mit der schlechtesten REPL daherkommt, die mir bisher bei allen Programmiersprachen begegnet ist! Die Kotlin REPL ist fehlerhaft und bei längerem Gebrauch auch häufig sehr langsam. Nicht mal grundlegendste Dinge wie korrekte Zeilenumbrüche funktionieren einwandfrei, wie das folgende Bild beweist:

Die REPL von Kotlin ist fehlerhaft und langsam.

Sinnlos und unübersichtlich ist es, die Eingaben 1:1 zu wiederholen:

>>> val list = listOf(1, 2, 3).map { it + 1 }
val list = listOf(1, 2, 3).map { it + 1 }

Die REPL von Scala ist dagegen ausgezeichnet. Sie beherrscht sogar die Auflistung aller für ein Objekt verfügbarer Methoden:

Die Scala-REPL funktioniert nicht nur einwandfrei, sondern enthält auch praktische Funktionen.

Hilfreich ist zudem, dass die Scala-REPL den Typ des zuvor eingegebenen Ausdrucks anzeigt:

scala> val list = List(1, 2, 3).map(_ + 1)
list: List[Int] = List(2, 3, 4)

Programmieren im Browser

Genau umgekehrt verhält es sich mit den Spielwiesen für das Web. Die Scala-Variante (Scastie) läuft alles andere als flüssig und muss nach jeder Änderung erstmal eine Datei speichern, mit der man eigentlich gar nichts zu tun haben will. Anschließend springt auf dem Server SBT an und führt gemächlich den Code aus. Für ein kurzes Beispiel mag das genügen, aber Spaß macht es nicht.

Scastie ist extrem träge.

Im Gegensatz dazu ist der Kotlin Playground eine wahre Freude. Dort läuft es nicht nur flüssig, sondern man bekommt sogar noch Syntaxvervollständigung und interaktive Überprüfung des Quelltextes dazu!

Das Gefühl einer Entwicklungsumgebung kommt bei Kotlin im Browser auf.

Aber so schön so ein Playground auch ist, so nebensächlich ist er am Ende doch für die Entscheidung für oder gegen eine Programmiersprache.

Funktionale Programmierung

In beiden Sprachen sind Funktionen “Bürger erster Klasse” und mit beiden kann man gut funktional programmieren. Trotzdem ist dieser Bereich einer der größten Unterschiede zwischen den beiden Sprachen. Scala ist viel mehr von funktionalen Programmiersprachen wie Haskell oder ML beeinflusst als Kotlin. So unterstützt Scala funktionale Programmierung durch ein paar zusätzliche Sprachmerkmale wie Currying und Pattern Matching. Vor allem aber ist funktionale Programmierung in Bibliotheken verankert und ist fester Bestandteil der Scala-Kultur.

Ein Beispiel dafür ist die Darstellung der Abwesenheit von Werten. In Kotlin wird dafür standardmäßig das wenig funktionale null verwendet, das trotzdem durch Prüfungen des Compilers sicher verwendet werden kann. In Scala wird dieser Fall üblicherweise mit dem Typ Option abgebildet, der für einen funktionalen Programmierstil ausgelegt ist.

Auffällig ist im Scala-Umfeld das Streben nach möglichst rein funktionaler Programmierung. In Kotlin wird funktionale Programmierung selbstverständlich an allen möglichen Stellen eingesetzt, aber es ist nie Selbstzweck und höheres Ziel.

Wobei auch Scala nicht so rein ist, wie es sich manche Enthusiasten wünschen, denn Scala ist nicht nur ein Hybrid aus objektorientierter und funktionaler Programmierung, sondern schleppt auch noch das Java-Erbe, zum Beispiel in Form von null und Exceptions, mit. Wenn man aber die Vorteile des Java-Ökosystems nutzen will, wird man immer wieder mit durch Java bedingten Kompromissen leben müssen. So gesehen sitzt Scala zwischen den Stühlen, denn es sagt nicht wirklich “ja” zur Java-Welt wie Kotlin, aber es ist eben auch nicht so rein funktional wie Haskell.

Typsystem

Die Typsysteme von Kotlin und Scala haben einige Dinge gemeinsam:

Einen wesentlichen Unterschied gibt es bei den Schnittstellen bzw. Traits. In beiden Sprachen ist es möglich, in einer Schnittstelle Methoden nicht nur zu deklarieren, sondern auch zu implementieren. Scala ermöglicht jedoch zusätzlich, auch Zustand in Form von Feldern zu definieren, womit eine eingeschränkte Form der Mehrfachvererbung möglich wird. Traits, die von demselben Trait abgeleitet sind, können “gestapelt” werden, sodass der eine den anderen dekoriert (Stackable Traits). Zusätzlich kann der Zieltyp, in den ein Trait “gemixt” werden kann, eingeschränkt werden. Dadurch kann der Trait auf Elemente des Zieltyps zugreifen (Self-Type).

Path Dependend Types gibt es nur in Scala. Mit ihnen wird ein innerer Typ von einer Instanz des äußeren Typs abhängig. Wenn man zwei Instanzen des äußeren Typs hat und davon ausgehend jeweils eine Instanz des inneren Typs erzeugt, sind die beiden inneren Typen nicht zuweisungskompatibel. Dadurch kann der Compiler in manchen Fällen noch strenger prüfen.

Structural Typing ist Scalas Variante von Duck-Typing. Statt die Implementierung einer bestimmten Schnittstelle vorauszusetzen, beschreibt man, welche Methoden ein Objekt anbieten muss, um akzeptiert zu werden. Wenn ein Objekt quakt und watschelt wie eine Ente, dann ist es eine Ente! Das kann sehr praktisch sein, um Typen, die zwar gemeinsame Methoden, aber keine gemeinsame (explizite) Schnittstelle haben, einheitlich zu behandeln.

Scalas Abstract Type Members sind Platzhalter für konkrete Typen, ähnlich wie eine abstrakte Methode ein Platzhalter für eine konkrete Methode ist. Im Prinzip kann man damit das gleiche tun, wie mit den bekannten Typparametern, nur dass man den konkreten Typ in einer API nicht zwangsläufig nach außen kommunizieren muss.

Nur in Kotlin gibt es dagegen “nullable Types". Mit ihnen wird für den Compiler explizit, welche Elemente den Wert null annehmen dürfen und welche nicht. So kann der Compiler garantieren, dass ein Element nie den Wert null annimmt, bzw. den Entwickler dazu zwingen, diesen Fall zu behandeln.

Scalas Typsystem bietet ein paar interessante Optionen mehr, in der Praxis kommt man in Kotlin aber auch gut ohne sie zurecht.

Zusammenspiel mit Java

Man kann in Kotlin und Scala problemlos Java-Code aufrufen. Andersherum ist es nicht ganz so einfach, denn beide Sprachen bieten Möglichkeiten, die es in Java nicht gibt. In Kotlin betrifft das relativ wenige Sprachmerkmale und meistens gibt es dann noch eine gute Brücke zu Java, wie die Annotation @JvmStatic, durch die eine statische Methode erzeugt wird. Scala ist deutlich weiter von Java entfernt, weshalb es schwieriger ist, mit Scala gute APIs für Java-Nutzer schreiben. Selbst vermeintlich grundlegende Dinge wie Enums oder Annotationen sind in Scala nicht zu ihren Java-Pendants kompatibel.

Während Kotlin ausdrücklich mit dem Ziel entwickelt wurde, gut mit Java zusammenzuspielen, hat man in Scala eher das Gefühl sich in einer Parallelwelt zu befinden. Eine wesentliche Ursache dafür sind die eigenen Collections, die nichts mit den Java-Collections gemeinsam haben, sodass man hin und her konvertieren muss (was immerhin implizit geht) oder in Scala mit Java-Collections arbeiten muss, was sich aber “falsch” anfühlt. So entwickelt man das starke Bedürfnis, nur noch Scala-Bibliotheken zu nutzen und begibt sich so weiter in die Parallelwelt. Nur ist diese Parallelwelt im Gegensatz zum Java-Universum relativ klein, sodass man doch immer wieder Java-APIs nutzen wird, was aber immer etwas unbefriedigend bleibt.

In Kotlin sind die Collections eher eine Hülle um Java Collections und bringen eine Fülle zusätzlicher Funktionen mit. Der Funktionsumfang und Nutzungskomfort ist in etwa gleichwertig zu den Scala-Collections.

Wie nah die Collections von Kotlin an Java-Collections bleiben, zeigen die nächsten Beispiele.

Der Typ einer Kotlin-Liste kann in einen Java-Listen-Typ umgewandelt werden (cast):

val kotlinList: List<Int> = listOf(1, 2, 3)
val javaList: java.util.List<Int> = kotlinList as java.util.List<Int>
println(javaList) // 1, 2, 3

Und eine Java-Liste kann an eine Methode übergeben werden, die eine Kotlin-Liste erwartet:

fun main() {
    val list = java.util.LinkedList<Int>()
    doit(list)
}

fun doit(l: List<Int>) {}

Von seinem Schöpfer, Tony Hoare, wurde null nachträglich als “Billion Dollar Mistake” bezeichnet, aber es ist in Java nun mal da und wird wohl auch nicht verschwinden. Wenn man die JVM als Plattform nutzen will, wird man also wohl oder übel damit leben müssen. Kotlin tut das sehr gut mit “nullable types”, während in Scala der funktionale Ansatz mit Option favorisiert wird. Option ist zwar sehr elegant und passt auch gut zu den Collections, aber es ist eben wieder ein Stück der erwähnten Parallelwelt – man möchte es dann überall haben und möchte keine Java-APIs mehr benutzen.

Dokumentation

Die Scala-API-Dokumentation ist sehr gut strukturiert und übersichtlich. Sie hat eine Suchfunktion und die Menge angezeigter Elemente kann mit einem Filter eingeschränkt werden. Erläuterungen sind eher kurz, reichen aber meistens aus. Hier und da wäre ein Beispiel schön.

Übersichtlich, mit Suche und Filterung findet man schnell, was man sucht.

Die Dokumentation der Sprache selbst ist bei Scala eher verstreut. Es gibt viele, teils schwer zu findende, Seiten zu einzelnen Themen, aber man hat nie das Gefühl ein Handbuch zu haben. Ich würde mir hier etwas wie das Rust-Buch wünschen. Immerhin gibt es gute (gedruckte) Bücher, allen voran “Programming in Scala”.

Die API-Dokumentation von Kotlin zeichnet sich durch eine sehr geringe Informationsdichte auf dem Bildschirm aus. Man muss sehr lange scrollen und es gibt weder eine Suchfunktion noch eine Filterung. Immerhin gibt es hier und da mal ein Beispiel, aber insgesamt sind sie spärlich gesät.

Kotlin-API-Dokumentation: platzverschwenderisch, keine Suche, keine Filterung

Punkten kann Kotlin hingegen mit der Referenz, die alle wichtigen Bereiche gut erklärt. Wer eine ähnliche Programmiersprache wie Java oder C# beherrscht wird mit dieser Dokumentation in der Regel auskommen.

Das Maß der Dinge ist die API-Dokumentation des .NET-Frameworks von Microsoft. Von dieser Qualität und insbesondere der Zahl der Beispiele sind sowohl Scala als auch Kotlin ein gutes Stück entfernt.

Beliebtheit und Verbreitung

Kotlin hat enorm an Popularität gewonnen, seit Google die Sprache offiziell für Android unterstützt. Im Mai 2019 wurde Kotlin sogar zur primären Sprache für Android erhoben.

Sprunghafter Anstieg von Kotlins Popularität seit Googles Ankündigung, Kotlin offiziell für Android zu unterstützen.

Über die Hälfte professionell entwickelter Apps ist bereits in Kotlin geschrieben. Damit ist Kotlin fest im Markt verankert. Aber auch abseits von Android breitet sich Kotlin aus. Das Spring Framework bietet zahlreiche Kotlin-Erweiterungen und die neue Konfigurationssprache von Gradle basiert auch auf Kotlin.

Scalas Verbreitung ist eng mit dem Batch-Processing-Framework Spark verbunden, was deutlich in der Stackoverflow-Studie erkennbar ist:

Scala und Apache Spark hängen eng zusammen – sonst nicht viel.

Bei den am meisten geliebten Sprachen landet Kotlin in der gleichen Umfrage auf dem 4. Platz, Scala kommt auf Platz 17.

Kotlin gehört zu den am meisten geliebten Sprachen.

Zum Abschluss noch ein paar weitere Ergebnisse:

Kategorie Kotlin Scala
Verbreitung 6,4 % 3,8 %
Geliebt 72,6 % (Platz 4) Scala 58,3 % (Platz 17)
Gefürchtet 27,4 % (Platz 24) 41,7 % (Platz 11)
Gewollt 11,1 % (Platz 5) Scala 4,3 % (Platz 17)
Gehalt $57K $78K

Solche Umfragen sollte man nicht zu ernst nehmen, aber sie geben zumindest einen Eindruck, wo eine Sprache steht und ob sie langfristig eher gute oder schlechte Chancen hat, sich zu behaupten.

Community

Zu der Kotlin-Community gibt es eigentlich nicht viel zu sagen. Ich empfinde sie als freundlich, hilfsbereit, konstruktiv und pragmatisch.

In der Scala-Community gibt es dagegen einige Spannungen und unterschiedliche Strömungen. Vielleicht gibt es die Scala-Community auch gar nicht, sondern eher getrennte Lager, die sich ab und zu begegnen. Wichtige Leute haben die Scala-Community frustriert verlassen. Diese Quellen vermitteln vielleicht einen kleinen Eindruck davon:

Das soll nicht heißen, dass es nicht auch sehr viele vernünftige Leute geben würde, aber das Ausmaß der Konflikte in der Scala-Community finde ich für eine Programmiersprache doch außergewöhnlich.

Blick in die Zukunft

Kotlin hat sich Rückwärtskompatibilität auf die Fahnen geschrieben, sodass kein Umbruch zu erwarten ist. Ich gehe davon aus, dass die Sprache nach und nach um um die eine oder andere nützliche Sache erweitert wird, aber so eine große und grundlegende Erweiterung wie Koroutinen scheinen in absehbarer Zeit nicht auf der Agenda zu stehen. Am interessantesten finde ich die Idee, dass der Compiler komplette Unveränderlichkeit von Datenstrukturen garantieren können soll. Am meisten Fortschritt erwarte ich bei Kotlin Native.

Die Kotlin Future Features Survey Results geben einen Einblick, wohin die Reise für die Sprache gehen könnte.

Durch die feste Verankerung im Android-Umfeld, die Unterstützung durch Spring, die leichte Erlernbarkeit und gute Werkzeugunterstützung, gehe ich davon aus, dass Kotlin sich stetig weiter ausbreitet. Es ist das, was schon einige andere Sprachen sein wollten, nämlich das bessere Java.

Bei Scala steht mit Version 3.0, alias “Dotty", ein grundlegender Umbruch ins Haus. Die Sprache wird dabei auf eine komplett neue theoretische Grundlage (Dependent Object Types (DOT)) gestellt. An manchen Stellen wird sie dabei verschlankt und an anderen erweitert. So sollen zum Beispiel Union Types hinzukommen. In dem Zuge wird auch ein komplett neuer Compiler entwickelt.

Ich denke, dass Scala 3.0 eine bessere Sprache als Scala 2.x sein wird. Aber ich sehe die Gefahr, dass Scala in das gleiche Dilemma geraten könnte wie Python mit dem etwa zehnjährigen Übergang von Python 2.x zu Python 3. Es wird mit Scala 3.0 einen großen Kompatibilitätsbruch geben und de-facto wird es eine neue Sprache sein, wenn sie auch dem heutigen Scala sehr ähnlich sein wird. Ob Unternehmen, die viel Geld in die Entwicklung heutiger Scala-Anwendungen gesteckt haben, bereit sein werden, für viel Geld zu migrieren, steht in den Sternen.

Keynote: The Last Hope for Scala's Infinity War - John A. De Goes

Zusammenfassung

Scala ist mächtiger und hier und da etwas eleganter als Kotlin. Dafür ist Kotlin wesentlich schneller erlernbar, verfügt über bessere Werkzeugunterstützung, spielt besser mit Java zusammen und führt eher zu einem einheitlichen Stil ohne Wildwuchs.

Dank der Unterstützung durch Google und das Spring Framework ist Kotlin fest verankert. Scala hat vor allen Dingen im Big-Data-Bereich Bedeutung, aber die Masse der Java-Entwickler hat die Sprache nie erreicht. Wohingegen Kotlin nach und nach die Gunst der Java-Entwickler zu erobern scheint.

Kotlin einzusetzen ist ein geringes Risiko. Die Einführung von Scala ist durch den erhöhten Lernaufwand, teilweise schwer verständlichen Code, schwächere Werkzeugunterstützung und die zu erwartenden Änderungen der Sprache riskanter.

Im Zweifelsfall ist man mit Kotlin auf der sichereren Seite.

Weitere Information