I’m a passionate Scala developer. I like the language and the concepts behind it and especially the stuff you’re able to do that only few general-purpose language support. Features like pattern matching, type classes, for comprehensions, or extension methods to which I have become accustomed and wouldn’t want to miss anymore.
However, in the course of time I have noticed that the percentage of Scala developers is not as large as I had imagined and expected it to be. Rather, it looks as if the number of active Scala developers is decreasing instead. Most people do not share this love for the language at all. Contrary to another language, which shines like a star in the programming sky. Of course, I refer to Kotlin. A language that exists for a much shorter period of time, but is much more positively perceived than Scala.
And I started asking myself: What could be the reason for this?
In conversations the following tendency towards Scala is prevalent: The code is so incomprehensible. Complicated. Hard to read. One is bombarded with concepts. The mindset is so different.
Evidently, getting started in Scala is seen as difficult. When we look at other popular languages, we see a similar pattern. Java, Python, C#, all languages that offer an easy learning curve. But is that the only reason?
In this blog post I will present several theses that contrast Kotlin and Scala on different levels. Maybe these theses can also be applied to other languages.
Acquirability and gentle learning curve attract new developers
If a programming language is not immediately understandable, it is not suitable for the mainstream.
Kotlin has chosen a very intelligent mix of familiarity and modernity. It is still an object-oriented language, just enriched with many concepts every Java developer dreamed of for so many years. Contrary to Scala, it does not require a different mindset. Java developers feel comfortable with the language in a second and are able to code productively much faster. Having Java and Kotlin side-by-side in a project is no hassle due to extensive compatibility and platform types. Additionally, nullable types solve the issues with null references consistently and type safely without having to resort to the more general concept of the Option type.
Scala, on the other hand, requires much more knowledge upfront. Not only is the type system substantially richer, also the language integrates many concepts from functional programming. You may write your code object-oriented or functional or mix both. The syntax lets you write the same code in many different ways, which offers flexibility of course, but requires the reader to be accustomed to more concepts and the language overall. And, of course, concepts like symbolical method names and implicits really do not foster code reasoning. Those concepts are useful in the hands of someone who knows how to use them conscientiously, but dangerous in others. Because if there is one thing that makes code difficult to read, it is code that violates the principle of least surprise. Scala simply does not offer an easy start for beginners and the overall attitude sometimes feels like “Take it or leave it” — start learning the concepts or you get lost.
Interoperability with existing ecosystems immensely facilitates developer access
If the language allows you to move in familiar paths, half the rent has already been paid.
The designers of Kotlin decided to incorporate the language into the existing Java ecosystem as much as possible. That becomes apparent very quickly. Calling Java code from Kotlin and vice-versa is fairly trouble-free. One might think that we are facing a big compatibility issue, since any reference type in Java may return null. But due to Kotlin treating types of Java declarations explicitly as platform types, we gain additional null safety when calling Java code from Kotlin. If we are dealing with a Java method, the safest approach would be to declare the type as nullable explicitly:
// declare type as nullable explicitly
val value: String? = javaObject.getValue()
This way, the compiler enforces us to handle the possible null value, which is not a bad idea from the programmers perspective.
// declare type as non-nullable explicitly
val value: String = javaObject.getValue()
We might as well declare the type as non-nullable, if we’re absolutely sure that the Java code cannot produce a null value here — either because we simply know this or because an appropriate nullability annotation has been declared on the Java-site that the compiler supports.
// let the compiler infer the type
val value = javaObject.getValue()
Even though this works, the compiler will welcome us with the following warning:
Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable.
If we see this warning, we should either specify the type explicitly or add a nullability annotation on the Java code, if possible.
Scala, unfortunately, does not distinguish between nullable and non-nullable types. No one stops us from calling a method on a value coming from Java. We, as developers, must be aware that there is some null handling required here. And the most idiomatic way to solve this issue in Scala is to wrap the value into an
Option — something that is considered an antipattern in Java.
But things get really weird when boxed types come into play:
val value = javaObject.returningNullableInteger()
# value: Integer = nullval value: Int = javaObject.returningNullableInteger()
# value: Int = 0val value = Option(javaObject.returningNullableInteger())
# value: Option[Integer] = Noneval value: Option[Int] = Option(javaObject.returningNullableInteger())
# value: Option[Int] = Some(0)
Unfortunately, thanks to an implicit conversion from
scala.Int, null becomes 0 in the second example, without us noticing. And the Option type does not help us here either. Usually, when we pass a null value to the option type, we get a
None return type like in the third example.
None denotes an
Option without a value. However, we see in the fourth example that the implicit conversion from
scala.Int takes effect first. The result is a
Some(0), where we would have expected
But there are other issues related to interoperability. One thing that immediately excited me about Kotlin was the compatibility of the collection frameworks of Kotlin and Java. No more calling
someList.asScala as is necessary in Scala. Kotlin’s collections are truly compatible due to some intelligent type mapping at compile time.
The last thing I want to address is the different experiences I have had in using frameworks. I once had the glorious idea to use the Spring Framework in Scala, because I really like the framework and the benefits it provides. Spoiler alert: It didn’t work out that well. It took quite a while until my simple example with a REST controller, service layer and database persistence was implemented due to many subtle details. And on top of that, it never felt like Spring and Scala really harmonised well. At the very latest when using JPA, the fun stops completely and you enter a world of pain. The entity class is paved with
@BeanProperty annotations, because this is simply required by the framework. We are forced to define a useless no-args constructor. And, of course, everything is mutable. A really sad story in a world that should otherwise be immutable. And then I combined Kotlin and Spring. It was like an enlightenment after this previous experience. Everything just worked at first go and everything looked so familiar. The framework support was orders of magnitude better.
Concluding, this sentence describes the problem quite well:
Scala was not designed with Java interoperability as a primary goal: it’s simply a byproduct of running on the JVM. Even though interoperability is used as a marketing argument to attract developers, in practice when moving to Scala, teams have to leave behind the Java ecosystem and adopt the Scala ecosystem in order to be productive. (Source)
Tooling support is key — Being backed comes handy
When making your first steps, it’s not the great language features that count, but how pleasantly you get started.
If we are honest, we are all spoiled by IDEs these days. They relieve us developers of so much work. And I wouldn’t want to miss them either. This might not hold for a minority of us who prefer to develop in vi or emacs because they are used to it, but it does for the vast majority. In 2004, as Scala arrived, there was a command-line compiler and that was all you got on the tooling site. Back then, the Scala core developers preferred to write their code in emacs, therefore no real effort was put in achieving proper IDE support. It took until the 20th of December 2011 until a robust and stable Scala IDE for Eclipse was available. Today, we can consider ourselves lucky that Typesafe (now Lightbend) at that time focused on providing useful IDE support. And a lot has happened since then. The Scala plugin for the IntelliJ IDEA is very mature. However, if you want to use NetBeans, you still have to deal with one or the other issue, because the plugin has many flaws.
Now let’s take a look at Kotlin in contrast. The company behind Kotlin is JetBrains, which also develops the IntelliJ IDEA; an IDE that has one of the largest market shares for JVM languages. From day one, JetBrains focused on good tooling support as a primary goal. And they had a good reason for that: Kotlin should drive sales of their enterprise IDE. Without doubt this is a very self-serving reason, but honestly, I gladly accept the bait. If it makes development easier for me, keep it coming. I don’t want to deal with an immature plugin for the language. I want to be able to concentrate on my work.
Google’s announcement of first class support for Kotlin on the Android platform in 2017 also contributed to the success, of course. And the fact that only two years later, Google announced Kotlin to be the preferred language for the Android platform, fired up the hype even more.
Kotlin did a lot better than Scala right from the start. Unfortunately, only a fraction of the developers give a language a second chance, so this lost ground is probably hard to make up.
Wrapping it up
The importance of a pleasant start and a smooth learning curve should never be underestimated. Here, Kotlin is the winner, there is nothing to be disputed. Kotlin is also well ahead in terms of interoperability. And even though the tooling support from Scala is now very good, it is difficult to compensate for initial errors. I am therefore not at all surprised by Kotlin’s success — and why Scala has fallen behind in this respect.