Rebuttal for Kotlin

PUBLISHED ON JUN 9, 2018 — JAVA, JVM, LANGUAGE DESIGN

Kotlin is the hot new language on the block by JetBrains, the creator of the Java IDE you most certainly use (or should be using). It brings null saftey and functional programming to a platform that really needs it, the JVM.

Since integrating Kotlin with Java is incredibly simple, companies across the board have begun switching to Kotlin. Working with Java APIs is incredibly easy, so you can keep all your old code while writing new code in a better language.

Well, can we really say that Kotlin is a “better” language? At Allegro, an online Polish retail site, they ventured into the world of Kotlin and they had such a wonderful time that… they went back to Java?

I have a personal love for Kotlin. I started programming Java about seven years ago, and much like many stubborn programmers I never saw a need to switch to Kotlin, until I tried it out. Writing in the language was so clean, so fast, so safe. After spending the last couple of years writing in Kotlin, I never want to go back.

I do, however, recognize that my opinion is not fact, and that other people can have differing opinions. Allegro is a company that tried Kotlin out, but they weren’t satisfied with the results. I’m here to give some counter arguments to the points they provided in their article, “From Java to Kotlin and Back Again”.

Here is the list of points they provided:

  • Name shadowing
  • Type inference
  • Compile time null-safety
  • Class literals
  • Reversed type declaration
  • Companion object
  • Collection literals
  • Maybe? Nope
  • Data classes
  • Open classes
  • Steep learning curve

I’m going to go through these one by one.


Name shadowing

Shadowing was my biggest surprise in Kotlin. Consider this function:

> fun inc(num : Int) {
>     val num = 2
>     if (num > 0) {
>         val num = 3
>     }
>     println ("num: " + num)
> }
> ```
> ...
> 
> Okay, so in Kotlin, inc(1) prints 2. The equivalent code in Java, won’t compile:
>
> ```java
> void inc(int num) {
>     int num = 2; //error: variable 'num' is already defined in the scope
>     if (num > 0) {
>         int num = 3; //error: variable 'num' is already defined in the scope
>     }
>     System.out.println ("num: " + num);
> }
> ```
> ...
> 
> In Kotlin, shadowing goes too far. Definitely, it’s a design flaw made by Kotlin team.

The first thing that I think of when talking about uses for name shadowing is _closures_.
I use shadowing all the time in closures! I could make different scopes for local variables,
but that's a lot more work than _just shadowing the name_.

kotlin // x used in some previous computation that is in the same scope val x = 7

xCoords/* : List */.forEachIndexed { i, x -> // good thing we can shadow variables! }


You really shouldn't write any code that puts you into a position where name shadowing
is an issue. The example they provided _would never happen in a real world scenario_,
and _real world scenarios often prove this feature useful_.

As [Lobsters](https://lobste.rs/) user [Leonidas](https://lobste.rs/s/rb3mpy/from_java_kotlin_back_again#c_kzv75a) says,

> In OCaml I use shadowing _all the time_, since when I shadow a value, I make it impossible
to access the previous value by accident. Especially if the type stays the same I _never_
run into the problem where I accidentally pass `num` around where instead I meant to use
`new_num` by the simple fact that the scoping just prevents me from messing up.

When you use a language that contains name shadowing, you become more concious of it
(especially if you have an IDE that annotates name shadows like IntelliJ) and errors
related name shadowing become non-existent.

---

### Type inference

The author notes that Kotlin's type inference is the same as Java 10's.

---

### Compile time null-safety

The main point in this section is that "_null types are confusing_".

> Things get nasty when your Kotlin code has to get along with Java code (libraries are
> written in Java, so it happens pretty often I guess). Then, the third kind of type jumps in — `T!`.
> It’s called platform type, and somehow it means `T` or `T?`. Or if we want to be precise, `T!` means `T` 
> with undefined nullability. This weird type can’t be denoted in Kotlin, it can be only inferred
> from Java types. `T!` can mislead you because it’s relaxed about nulls and disables Kotlin’s
> null-safety net.

The relaxation that Kotlin gives you with Java APIs allows the user to declare nullability
for types from a platform that doesn't offer it. As a consumer of a Java API, you specify
the nullability and handle it yourself. If you know a Java API will never return null
(maybe it uses contracts like `@NonNull`), Kotlin allows you to use it as such. The
opposite is also true: if your API can return null, you can specify that it returns null.

> Consider the following Java method:
> 
> ```java
> public class Utils {
>     static String format(String text) {
>         return text.isEmpty() ? null : text;
>     }
> }
> ```
> Now, you want to call `format(String)` from Kotlin. Which type should you use to consume
> the result of this Java method? Well, you have **three** options.

They mention using `String!` as a third type, but really that's not how it's used.

> **Third approach**. What if you just let the Kotlin do the fabulous local variable type inference?
> 
> ```kotlin
> fun doSth(text: String) {
>     val f = Utils.format(text)      // f type inferred as String!
>     println ("f.len : " + f.length) // compiles but can throw NPE at runtime
> }
> ```

Personally, I've never "used" a `T!` because they don't exist in practicallity. Here,
the author is using the result of `Utils.format(text)` as a `String`.

This leaves you, the consumer, with _two_ options: `T` or `T?`. How does one go about
deciding which one to choose? Oh yeah, **RTFM**. Read the documentation and the code
if you want to know how to use a method! You can obviously tell that function can
return null, so you want to mark the return value as nullable.

> ```kotlin
> fun doSth(text: String) {
>     val f: String? = Utils.format(text)   // safe
>     println ("f.len : " + f.length)       // compilation error, fine
>     println ("f.len : " + f?.length)      // null-safe with ? operator
> }

The author also notes that you can’t call methods or fields from a nullable type without dealing with the possibility of null. This is a core language feature, but the author’s tone seems to indicate that this is a hassle to deal with. In my personal projects, I have absolutely loved dealing with null types using ?.let and :?.


Class literals

Reflection is used in a lot of libraries and Kotlin has a way to obtain the Java Class of an object.

val javaClass : Class<Thing> = Thing::class.java

The author thinks that the syntax is too much, and apparently this is enough to revert back to Java. I personally don’t understand this mentallity. It’s not much more typing than .class, and we own IDEs with auto completion. This is really a non-issue.


Reversed type declaration

Another stubborn viewpoint of newer languages.

Standard notation in Java:

> int inc(int i) {
>     return i + 1;
> }
> ```
> Reversed notation in Kotlin:
> ```kotlin
> fun inc(i: Int): Int {
>     return i + 1
> }
> ```
> This disorder is annoying for several reasons.

"_ahhhh oh noooooo!!! the world is ending!!!!_"

It's just syntax.

> **First**, you need to type and read this noisy colon between names and types. What is the
> purpose of this extra character? Why are names **separated** from their types? I have no
> idea. Sadly, it makes your work in Kotlin harder.

I was somewhat astonished when I first read this. One character makes things _that tough_?

The colon operator notates that _`a is of type A`_. It makes things rather clean in regards to
type inference.

java public void test() { String s = “test”; var a = 5; Object o = 2; }


In Java 10, we have a keyword taking the place of an identifier! What if I had a class called
`var` that I wanted to use? <small>("_That would never happen!_" - You've never dealt with obfuscated
code.)</small> This creates an inconsistency that doesn't exist in Kotlin.

kotlin fun test() { val s: String = “test” var a = 5 val o = 2; }


Here, I can specify the type if I want to. A good example for why this is handy would be with
subclasses.

kotlin // B extends A fun g() { val super: A = B() // use super as an A }


You can do the same in Java, but it's more consistent in Kotlin.

> <b>The second problem.</b> When you read a method declaration, first of all, you are
> interested in the name and the return type, and then you scan the arguments.

This makes sense, but finding the return value of a function isn't difficult.

> In Kotlin, the method’s return type could be far at the end of the line, so you need to scroll:
> ```kotlin
> private fun getMetricValue(kafkaTemplate : KafkaTemplate<String, ByteArray>, metricName : String) : Double {
>     // ...
> }
> ```

Firstly, the _name_ of the function should give some hint to the return value.

Second, this _isn't a problem_. I've honestly never had this issue while programming in Kotlin.
If you have this issue, here are some options for you:

* turn on soft-wrap
* get a bigger monitor
* make your font size smaller
* quit programming, it's not for you

> Or, if arguments are formatted line-by-line, you need to search. How much time do you need to find the
> return type of this method?
> ```kotlin
> @Bean
> fun kafkaTemplate(
>         @Value("\${interactions.kafka.bootstrap-servers-dc1}") bootstrapServersDc1: String,
>         @Value("\${interactions.kafka.bootstrap-servers-dc2}") bootstrapServersDc2: String,
>         cloudMetadata: CloudMetadata,
>         @Value("\${interactions.kafka.batch-size}") batchSize: Int,
>         @Value("\${interactions.kafka.linger-ms}") lingerMs: Int,
>         metricRegistry : MetricRegistry
> ): KafkaTemplate<String, ByteArray> {
> 
>     val bootstrapServer = if (cloudMetadata.datacenter == "dc1") {
>         bootstrapServersDc1
>     }
>     ...
> }
> ```

I found the return type instantly because it's not indented like the arguments are. After using languages
with `:`, you get used to where the types are.

> The third problem with reversed notation is poor auto-completion in an IDE. In standard notation,
> you start from a type name, and it’s easy to find a type. Once you pick a type, an IDE gives you 
> several suggestions about a variable name, derived from selected type. So you can quickly type
> variables like this:
> ```java
> MongoExperimentsRepository repository
> ```
> Typing this variable in Kotlin is harder even in IntelliJ, the greatest IDE ever. If you have
> many repositories, you won’t find the right pair on the auto-completion list. It means typing the
> full variable name by hand.
> ```kotlin
> repository : MongoExperimentsRepository
> ```

I will say I have experienced this issue before, but I type at 90 WPM so it isn't an issue for me.

---

### Companion object

In Kotlin, you have two ways to declare a variable that isn't tied to the instance of a class:

* companion objects
* top level declarations

The author seems to have completely forgotten the second solution. I am personally fine with
writing variables associated with classes in companion objects, but I guess it's too much for the
author.

> Sometimes, you **have to** use static. Old good `public static void main()` is still the only way
> to launch a Java app. Try to write this companion object _spell_ without googling.
> ```kotlin
> class AppRunner {
>     companion object {
>         @JvmStatic fun main(args: Array<String>) {
>             SpringApplication.run(AppRunner::class.java, *args)
>         }
>     }
> }
> ```

Top level declarations still exists. You can throw `main` at the top of your file at it'll run fine.

kotlin fun main(args: Array) { SpringApplication.run(AppRunner::class.java, *args) }


It's quite simple.

And the _"spell"_ is rather simple too, once you know what everything does.

---

### Collection literals

> Kotlin:
> ```kotlin
> val list = listOf("Saab", "Volvo")
> val map = mapOf("firstName" to "John", "lastName" to "Doe")
> ```
> In maps, keys and values are paired with the `to` operator, which is good, but why not use
> well-known `:` for that? Disappointing.

Java is absolutely awful when it comes to initializing collections, and Kotlin provides some nice functions
to help with that. Instead of providing special syntax for it, Kotlin uses inline functions that are
built into the standard library. This cleans up the syntax and saves characters for more important things.

Using functions instead of characters also allows you to specify what data structure you want.
How are you going ot have special syntax for `List`, `MutableList`, `Map`, `MutableMap`, and `Array`?

---

### Maybe? Nope

I've been writing a lot of Scala code recently, and I've found that _Option Data Structures are unsafe
when they provide a `get` function_. Let's look at some example code.

java public int genereateInt() { Optional o = Optional.ofNullable(null); return o.get(); }


All safety is lost when the interface provides such a function. With Kotlin's nullable types, this
state is unrepresentable.

kotlin fun generateInt(): Int { val o: Int? = null return o }


This would create a compile time error.

You _can_ get arround the null safety with `!!`, but if you're using this operator you're probably
doing something wrong. <small>(It mainly exists for Java interop.)</small>

kotlin fun generateInt(): Int { val o: Int? = null return o!! }


This _will_ throw an exception at runtime. Using `!!` is bad practice, so you shouldn't run into this
issue.

> Typically, when you have an Optional, you want to apply a series of null-safe transformations
> and deal with null at the and.
> 
> For example, in Java:
> ```java
> public int parseAndInc(String number) {
>     return Optional.ofNullable(number)
>                    .map(Integer::parseInt)
>                    .map(it -> it + 1)
>                    .orElse(0);
> }
> ```

The author goes through some examples of how to convert this into Kotlin, but his final result
isn't clean. Here's how I'd write it.

kotlin fun parseAndInc(number: String?): Int = number?.toIntOrNull()?.inc() :? 0 ```

This doesn’t account for Integer.parseInt throwing an exception, but that doesn’t have anything to do with Kotlin’s null safety.


Data classes

The author identifies that Data Classes are useful, but have limitations. Not much I can say here.


Open classes

Inheritance syntax looks like this:

open class Base

class Derived : Base()

Kotlin changed the extends keyword into the : operator, which is already used to separate variable name from its type. Back to C++ syntax? For me it’s confusing.

Using : with inheritence aligns with the usage of it for type annotations. Here, we’re saying Derived is of type Base, which makes sense: Derived is inheriting from Base.


I hope I showed that a lot of these “complaints” are really just stylistic choices that some people may be scared of. In reality, experiencing different languages can help you grow as a programmer, and can introduce you to new ways of thinking. It sounds like the people at Allegro were trying to write Java code in Kotlin rather than trying to write Kotlin code. I’ve been in this mindset before, and it’s not a great one. It’s better to accept the paradigms and ideas that a language presents and run with them. If you’ve learned one thing from this article, it’s that you shouldn’t shove one language into another, but you should learn what the new language has to offer.