June 27, 2016

Playing with Kotlin

Today there are many JVM languages, and to be fair I have a strong opinion on most of them. My first non-Java JVM language was Scala, which I still have mixed feelings about. I was just very turned off by the language since it has many powerful features but when I was trying to make it my main language I was constantly plagued with issues of tool support for an IDE. Groovy did not impress me because I just simply cannot see the value in dynamic languages beyond smaller projects. I prefer a compiler catching an entire category of bugs and avoiding having to look at documentation to tell me what type my third-party library is returning to me or what types the parameters should be. It is why I really enjoy using auto for locals and lambdas in C++ and var in C#. Sometimes the type doesn't matter to me, or it is so blatantly obvious I don't see why I would need to declare it.

Unfortunately, C# (or at least to my current knowledge) does not have the concept of constant local variables. I tend to like knowing up front if a variable is going to change it's value over the scope of the function, it means I have less things to think about as I'm evaluating code. Java is only just starting to dip it's toe into type inference, but as a whole the language is terribly verbose. Then on top of that you have a large part of the Java community that believes everything should be abstracted to the point of meaninglessness. If I need to look at more than a file or two to know what I am working with then there is simply too much abstraction. As they say, less is more, and being concise while maintaining clarity is a heck of a lot easier to maintain than abstracting away all the details. Naturally, if you are writing a public interface you should make it abstract though the extent to abstract really depends on the language and the need to maintain an ABI or API.

I like functional features, but I'm still not set on using a functional language which is why I don't use Clojure (which again is dynamic so another point against it). Functional languages are good at solving some problems but can very overly verbose at solving others, where a loop and state would just be simpler. I played with Xtend for a while, and it seemed to fit right where I would like a language to fit. However, I got frustrated by how fragile the compiler was, and it was decently supported I just couldn't put faith behind it or trust that the project will be maintained for a long time. At the time I was using it I heard about Kotlin and I read a few things about it, but realized it hadn't been around for very long and the developers were not maintaining backwards compatibility. However, that all changed recently when they were preparing their release candidates for version 1.0 and at that point they would be backwards compatible. I was using Intellij after being a long time user of Eclipse, and to be fair having the ability to set Eclipse key bindings has made my transition to JetBrains' tool set very straightforward.

I used Eclipse for almost 10 years, and over the years I have tried other IDEs for many different languages. I did use WebStorm for Javascript a few years back and was quite impressed by it. PhpStorm I more recently used and was continued to be impressed by how solid of an IDE the Intellij derivatives were. After giving Intellij another shot recently I was simply blown away at the quality and the performance of the IDE, especially with how easy it was to manage builds and libraries. Then I tried Kotlin. I was expecting to find Kotlin + Intellij similar to Xtend + Eclipse... and I couldn't have been more wrong. The experience so far after using it for about 8 months has been nothing but solid with the odd minor hiccup that were extremely easy to workaround or resolve. The issue tracker for JetBrains' products and seeing the responses and watching fixes has been very impressive.

The only thing that really drives me nuts from JetBrains these days is Reshaper++ and Visual Studio... However, I feel most of the problems are from working on very large solutions in Visual Studio and not from the Resharper++ plugin. If I could make use of CLion for building with the Visual Studio tool chain I could only dream of how much more productive I would be in C++. I have debated trying to make everything work with QtCreator, which is a decent IDE, but I would rather spend the time coding than trying to make my tools work.

Kotlin brings a number of things to the table that I just have a hard time finding with most other languages. The first thing is Java interoperability being treated as a first class citizen. This means using Java libraries is extremely simple and straight-forward. It also means using the JDK isn't frowned upon or difficult. I can reuse my knowledge from Java without having to learn yet another standard library. Kotlin supports extension functions and properties, which simply means using the JDK isn't difficult because it has been well extended by Kotlin to offer very use and powerful features that you simply don't have in other JVM languages. An example is converting between lists and arrays, something dead simple in Kotlin, but painful in Java and annoying in other languages.

Type inference and null checking are my favorite features, but next on the list would be method and property extensions. I think an example is more useful here so let there be some example code:

fun Double.abs(): Double = Math.abs(this)

I will assume basic Java knowledge when talking about the Kotlin example code. The above is a function definition which extends the double type (notice I'm talking about double and not Double) to have a method called "abs". This method is defined as calling Math.abs on itself and returning the value. A few examples:

println((3.0).abs()) // Prints 3.0
println((-2.0).abs()) // Prints 2.0
var x = 2.0 / -1
println(x.abs()) // Prints 2.0

Now of course those familiar with Java will be freaking out about auto-boxing. My response is, there isn't any. The Kotlin compiler is pretty smart about that. It simply will replace the code to look roughly like:

System.out.println(Math.abs(3.0)); // Prints 3.0
System.out.println(Math.abs(-2.0)); // Prints 2.0
double x = 2.0 / -1;
System.out.println(Math.abs(x)); // Prints 2.0

Coding in Kotlin really feels like Java but with a ton of syntactic sugar and a very smart compiler that will make intelligent choices for you. The above example is extremely simple, so let's see a more complicated example:

inline fun < reified T : Lock, reified R> T.useLock(task: () -> R) : R {
    try {
        this.lock()
        return task()
    } finally {
        this.unlock()
    }
}

Unfortunately, I don't have a good syntax highlighter for Kotlin so you will have to accept pre-tagged code. The above probably looks pretty strange to a Java developer. You can probably guess it is a function definition. However, has the "inline" keyword on it, which means the Kotlin compiler will inline the function. The generic signature is two parts: first, "reified T : Lock" which means capture the type of T which is a subclass of Lock. In this case "java.util.concurrent.locks.Lock"; second, "reified R" which means capture the type of R. This means unlike regular Java generics, you actually have full type information of the generic parameters in this function and they are not erased. Now, if you are a Java developer your jaw should drop from that statement.

The function extends all types of type T, which means anything that is or extends "java.util.concurrent.locks.Lock". You will need to import the definition to make use of it, but Intellij will automatically import it for you when you need it. The function will be called "useLock". It will take a lambda that has no parameters and returns a value of type R. The function itself returns a value of type R. The body should be fairly straight forward, it calls the lock and unlock method of the object's it extends and invokes the lambda in the critical section then returns the value from the invoked lambda. Long story short, you now have a method that applies automatic lock management to any locks you might consider using. So how would you make use of this? Well here is an example:

fun requestInputKey(key: String): Int {
    return readLock.useLock({
        inputKeyIdMap[key]
    }) ?: writeLock.useLock({
        val value = inputKeyIdMap[key] ?: currentKeyId++
        inputKeyIdMap[key] = value
        value
    })
}

I will leave it as an exercise to the reader to fully understand the details of the above code. However, it basically provides a read-write lock around a map. If the value key is defined in the map, it acquires the read lock and returns the integer that it maps to. If the key is not found in the map, it upgrades the read lock and acquires the write-lock. It rechecks the value, and if it doesn't exist it generates a new key value, assigns it to the map and returns the value. A comparable example in Java can be found on the JDK8 docs page for ReentrantReadWriteLock, just look at the first example usage and compare it with the above code. The above example has significantly less code, and given the inlining and reified types it doesn't incur any overhead over the Java code. Moreover, I find it significantly easier to understand as I don't need to look at all the try/finally blocks or worry about null values. Nulls have to be explicitly specified and checked in Kotlin, so say goodbye to the famous NullPointerExceptions.

There are a number of other useful extensions that can be written such as automatic resource closing (keep in mind Kotlin runs on Java 6), automatic memory management for objects which need to be explicitly deleted, suppression of exceptions, method reference parameters, and more. If you want to give it a try you can run it in the browser (did I mention they are polishing their Kotlin to JavaScript compiler?). Months later, and thousands of lines of code later I continue to be impressed by the language and can't wait for what it will bring to the table in the future.

No comments :

Post a Comment