jadon

polyglot plt hacker

Home · Blog · GitHub · Twitter · Email


Rebuttal for Kotlin

Published Saturday, June 9, 2018 · 3297 words, 18 minute read.

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 start 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:

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:

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.

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

xCoords/* : List<Int> */.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 user Leonidas 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:

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?

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.

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:

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.

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? (“That would never happen!” - You’ve never dealt with obfuscated code.) This creates an inconsistency that doesn’t exist in 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.

// 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.

The second problem. 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:

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:

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?

@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:

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.

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:

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.

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.

fun main(args: Array<String>) {
    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:

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.

public int genereateInt() {
    Optional<Integer> 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.

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. (It mainly exists for Java interop.)

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:

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.

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.



Home · Blog · GitHub · Twitter · Email