Kotlin from Scala perspective: Advent Of Code 2023

Voytek Pituła
7 min readJan 6, 2024

--

For the last few years, I have considered Kotlin a language very close to Scala: it runs on JVM, it’s strongly typed, it has immutable collections, and some FP community. However, what I lacked was real(ish) exposure. So, this year, I decided to participate in Advent Of Code. The goal was simple: evaluate how much pain (or pleasure) a seasoned Scala developer would feel if they switched to Kotlin. All the code from this experiment is available here.

Disclaimers

What you will read below is based purely on the exposure allowed during Advent Of Code. This means I have no real understanding of the community, ecosystem (library availability, maturity, trends), or more specialised language features. I also haven’t read a single book or a proper tutorial about the language. At the same time, this might be a common perspective and a valid data point — a lot of people will come to the language without all that knowledge.
I tried to make my assessment as objective as possible, but it’s impossible to stay fully unbiased. I have a decade of experience writing in Scala, and I also liked Kotlin even before I tried it (don’t ask why, I just did 🤷). Also, a lot of things below might be simply wrong. I rely on the first page of Google or the first answer from ChatGPT to find the information. If it’s not there, I might have missed it. But this is also a data point for anyone who would like to make this information more visible.

Kotlin is More Practical than Principled

This can be treated as the main point of my evaluation. This is both a good and a bad thing. Let’s see why.

Mutability is Too Easy, Immutability Too Hard

I’m not a purist and I have no problem with tactical mutability. It’s often not only needed, but often necessary to write performant code. However, using mutable state should be an exception that is possible but discouraged, so that developers can stay on the right path by default. So, why do I make this claim?

  • Constructing mutable collections is available by default (without import)
  • Immutable collections are not perfect
    - There’s no takeUntil on generateSequence
    - There’s no immutable, append-optimized collection in stdlib (e.g. Vector from Scala)
    - There’s no immutable sorted map (SortedMap in Scala)
  • Functions require explicit return by default if you need a code block instead of a single expression
    - This blurs the feeling of being expression-first
    - I needed 2 weeks to learn the run {} trick that allows you to get an implicit return within a block by defining all functions with fun foo() = run { }
  • tailrec is not perfect
    - It has to be applied for optimization to take effect (@tailrec in Scala guarantees optimization but is not a prerequisite for it)
    - If a function is not tail recursive, the tailrec is ignored and we get a StackOverflowError in runtime (in Scala we get a compilation error)
    - It doesn't work with the = run { } trick mentioned above, or at least I wasn't able to make it work

Does this mean the language is unsuitable for functional programming? Not at all. I managed to write most of my code as immutable. I also looked at a few random Kotlin AoC repositories on GitHub, and most of them were immutable in style. So, it's more than possible, we just need to be prepared that the experience will not be as smooth as in FP-first languages.

Nulls Are Not That Scary

Kotlin decided to embrace nulls and give them first-class compiler support, instead of following Scala's approach with Option being deeply integrated into the standard library. While both approaches have different trade-offs (you can see this in an old post from Odersky himself), I personally believe it's good to have both. This is not a significant problem in practice though. Yes, you can't express Option[Option[A]] this way, but it's not something we do every day (and a custom ADT might be a better approach anyway). At the same time, an API based on direct null support feels more native and simpler.
Overall, this is an example of a different mindset. Option is a standard user-space abstraction that can be dealt with generically (it's just a type of kind * -> *). This can't be said about T?. On the other hand, T? requires no additional allocations which can matter a lot for low-level performance. And it comes with lower mental load (no need to understand a new data type).
So, be prepared to see a lot of nulls in Kotlin APIs, but don't be scared of them. It's nothing like Java that gave many of us PTSD in that regard.
Side note: one thing I'm disappointed about, is that Kotlin didn't learn from Scala's mistakes and exposed unsafe variants of methods. Right now, you have both reduce and reduceOrNull, while only one is safe to use on an empty collection. We have the unsafe variants in Scala because we can't remove them without breaking compatibility. Kotlin had a chance to avoid this mistake but didn't take it.

Specialisation over generic abstractions

The first thing you notice when performing any operation in stdlib is that there are a lot of method variants. Example?

List::map
List::mapNotNull
List::mapIndexed
List::mapIndexedNotNull
List::mapTo
List::mapNotNullTo
List::mapIndexedNotNullTo

This is a very practical approach that has certain benefits:

  • It allows for better performance.
  • It doesn’t require sophisticated abstractions.

However, at the same time, it’s not general and composable. Scala has a long tradition of building APIs from composable pieces and trying to keep the API rather minimal.

mapNotNull -> flatmap(f: A -> Option[B])
mapIndexed -> zipWithIndex.map
mapTo -> map.to(...)

Kotlin’s approach allows for far fewer allocations at the cost of an increased API surface. My personal preference is with Scala, as I’m rarely ever concerned about CPU or memory-level performance (most of my projects involve network access, which slows everything down enough that allocations no longer matter). But it’s not an unreasonable tradeoff to make.

This preference for specialization is visible in other places as well:

  • Collections are constructed through dedicated top-level methods (e.g., listOf instead of List.apply).
  • Arrow has a dedicated “for comprehension” for each monad.
  • Enums have first-class support instead of just being ADTs (this is the same approach as Scala 3 took, which I don’t necessarily like).
  • Destructuring: any type can participate in val (a, b) = x by defining componentN methods. We have this through more generic pattern matching.
  • Operator overloading is limited to a predefined list.

Pattern Matching is Minimal

To be precise, it’s minimal enough to provide significant value without introducing major complexity to the language. It allows you to type-test values and write more concise if else chains, but it does not allow for sophisticated extractions. This is one more example of a tradeoff: focus on simplicity at the cost of expressiveness and power.

It’s a Young Language

This implies two things: it’s more modern and less mature.

It Does a Lot of Things a Bit Better

I’ve noticed quite a few small differences that are, in my opinion, superior to what we have in Scala:

  • Char::code and Char::digitToInt are much more explicit than Scala's Char::toInt
  • No implicit widening (e.g., no implicit conversion from Int to Long) — we learned it’s not a good idea the hard way
  • Iterable::groupingBy is a nice API for "map reduce" kind of operations
  • TODO() instead of ??? - not a big deal but slightly less cryptic
  • { it } lambda syntax is both more readable and easier to use. It doesn't have many caveats that we hit with _
  • Classes have to be marked as open explicitly to allow for inheritance.

These small things make writing Kotlin code a quite pleasant experience. And I haven’t touched a lot of more advanced stuff that looks really cool: coroutines, inline functions, reified/in/out generic parameters, receivers, and probably much more.

It Lacks Some Things

I believe Kotlin didn’t have enough time to develop all the things we have in Scala. A few examples are given above in the context of immutability. An additional significant issue is lack of parallel collections/operations available in the stdlib.
What’s surprising is that the tooling, or more precisely, Kotlin’s support in IntelliJ, is not in any way superior to Scala. I didn’t notice anything better, although I did encounter a few cases where highlighting was incorrect (similar to what we had in Scala a few years ago). It’s surprising knowing that JetBrains created the language.

It Has Most of the Things I Need

One of the most important things for me is the ability to model the domain well. I found all the necessary components: data classes, typealiases, newtypes (through value classes), extension methods, sealed interfaces and classes, static objects, and more.
It also comes with a rich enough ecosystem. I found at least two parser combinators libraries, it has Arrow for functional programming. It even has a Scala collection port called dexx. And of course, it allows for using any Java library.
In the end, I was able to find all the important mechanisms/libraries to write code in a way that I like. I even managed to write a nice little DSL for z3 using context receivers (think scala implicits) and operator overloading.

// Java
solver.add(
ctx.mkEq(
ctx.mkAdd(mx, ctx.mkMul(mxv, t)),
ctx.mkAdd(
ctx.mkReal(sx.toString()),
ctx.mkMul(
ctx.mkReal(sxv.toString()),
t
)
)
)
)
// Kotlin DSL
with(ctx) {
solver.add((x + vx * t).eq(sx.asZ3() + sxv.asZ3() * t))
}

Summary

Kotlin is not Scala; it follows different tradeoffs and goals. It focuses more on performance and being easy (or even simple for some definition of simplicity) at the cost of not being extremely generic, principled or powerful.
I believe it’s still the best fallback choice for Scala developers. It allows us to write safe, concise, strongly typed, functional, object-oriented and readable code that closely reflects the business domain. And it allows this without much friction. I would say it’s much better from that perspective than Typescript, Haskell, Clojure, or any other language I know.
But approaching it with the right understanding and expectations will make everyone’s life easier. Any attempt to write Scala in Kotlin will yield results similar to those when writing Haskell in Scala — it’s possible but (sometimes extremely) painful.

If you’re interested in more content related to Functional Programming and a good dose of crap posts, check my Twitter profile.

--

--

Voytek Pituła

Generalist. An absolute expert in faking expertise. Claimant to the title of The Laziest Person in Existence. Staff Engineer @ SwissBorg.