EngineeringFantasy

Kotlin Koans I - The Beginning

Saturday, 22 November 2014

Kotlin is a programming language created by Jetbrains. It runs on both the Java Virtual Machine (JVM) and can be compiled to JavaScript.

I've been meaning to try out Kotlin for quite some time. After my foray into Scala, which brought some very dramatic changes to the Java universe, my interest in JVM based languages was re-invigorated.

Back in 2013, Kotlin seemed like an interesting language because Jetbrains, a heavy investor in the JVM, was promoting it as an industry first programming language. I knew Kotlin also was going to have excellent tooling support, because after all Jetbrains is an IDE company. It wasn't until recently that Kotlin had a tutorial series called the Kotlin Koans.

So why should you learn Kotlin? Well that is what I will be trying to answer throughout this series. We will go through the Kotlin Koans, and find out the differences between Java and Kotlin as well as other languages, but in short [1]:

  • No semicolons
  • First class functions, meaning they do not need to be declared within classes
  • Lambdas
  • An awesome collections library
  • Small runtime
  • data classes, like case classes in Scala
  • Good interoperability with existing Java code

Getting Started

Installing Kotlin

The simplest way to install Kotlin is to download IntelliJ IDEA. The community edition supports Kotlin so you can go ahead and download it. After this, we can install the Kotlin plugin, which will automatically install the Kotlin runtime as well:

Installing the plugin. The steps are indicated by the numbering.

Hello World in IntelliJ IDEA

Once this has been installed, getting started with your first hello world project is simple. Hadi Hariri [2] has created a bunch of videos on getting started. Here, he demonstrates how to create a new Kotlin project:

Dash doctests

Kotlin also has Dash [3] doctests, provided by a third party:

Installing Kotlin doctests

However, this merely is a copy of the online documentation available for Kotlin, and does not actually have a searchable API reference. The code completion in IDEA somewhat mitigates this though. The Jetbrains team is still working on modernizing the reference for Kotlin at the moment.

Cloning the Koans

The next part is just cloning the Koans repository. One can just use the command line like so:

git clone https://github.com/jetbrains/workshop-jb

Or checkout from the IDEA start menu:

Checking out from git Setting the repo url

Koans Part I: Introduction

I'm going to go through all the exercises in the Koans. I will share how I solved these problems, with the challenges I've had to face as well as examples of solutions themselves. One can merely continue reading to see a list of features that Kotlin has, or one can follow along using the consecutive sections as an answer sheet.

0: Functions

The goal of the Koans is to make all the tests pass. Lets head over to our first exercise:

Where the file is located

Inside the file, we have todoTask0 and task0. todoTask0 gives us the information we need to complete the task. We essentially need to make it return "OK". This seems easy enough:

fun task0(): String {
    return "OK"
}

That was easy. We have a function here that returns a String.

1: Collection to String

In this task, we need to convert a given collection into a string. We are allowed to copy and paste the Java code and transform it into Kotlin code:

You can just copy-paste it and agree to automatically convert it to Kotlin - but only in this task :).

It is unfortunate, however, that todoTask1 does not give us any extra information other than telling us to just "Rewrite JavaCode1.task1 to Kotlin". So, I decided to take a look at the test function located here:

Where the tests are located

We can see that we are asked to turn the collection of numbers into a comma delimited string, with braces at the beginning and at the end:

class _01_Functions() {
    test fun collection() {
        Assert.assertEquals("{1, 2, 3, 42, 555}", task1(listOf(1, 2, 3, 42, 555)))
    }
}

Copying the Java code from JavaCode1.task1 results in this:

val sb = StringBuilder()
sb.append("{")
val iterator = collection.iterator()
while (iterator.hasNext()) {
    val element = iterator.next()
    sb.append(element)
    if (iterator.hasNext()) {
        sb.append(", ")
    }
}
sb.append("}")
return sb.toString()

Not very different from the actual Java code. However, I chose to use the following function:

fun task1(collection: Collection<Int>): String {

    val sj = StringJoiner(", ", "{", "}")

    for (item in collection) {
        sj.add(item.toString())
    }

    return sj.toString()
}

I don't know if my version has worse performance than the converted Java Code, but it sure passes the test:

Passing test 1

In this I chose to use Kotlin's for loop that supports collections.

2.1: Default Parameters

In this exercise, we need to convert an overloaded Java class into a simple Kotlin function with default parameters. The function in question is foo:

fun foo(name: String): String = todoTask2_1()

At first this was confusing because it had been a long time since I had used a language that did not have default parameters. Secondly, foo already seemed to have a function body. Thirdly, there seemed to be no instructions on what the function did; you seemingly had to read the Java code to understand. I decided to go back to the tests again and see what was up.

We have to rewrite the function foo to have three parameters:

  • name which is a String. Required
  • toUpperCase which is a Boolean value. Optional, defaults to false
  • number which is a Int. Optional, defaults to 42

With that out of the way, this is what I ended up with:

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false): String {
    return if (toUpperCase) name.toUpperCase()+number.toString() else name+number.toString() // 1
}

fun task2_1(): String {
    return (foo("a") +
            foo("b", number = 1) +
            foo("c", toUpperCase = true) +
            foo(name = "d", number = 2, toUpperCase = true))
}

In 1 I use a ternary operator, the equivalent in a full if-else statement would be:

if (toUpperCase) {
    return name.toUpperCase() + number.toString()
} else {
    return name + number.toString()
}

Compared the above to the amount of Java code one would have to write:

public class JavaCode2 extends JavaCode {
    private int defaultNumber = 42;

    public String foo(String name, int number, boolean toUpperCase) {
        return (toUpperCase ? name.toUpperCase() : name) + number;
    }

    public String foo(String name, int number) {
        return foo(name, number, false);
    }

    public String foo(String name, boolean toUpperCase) {
        return foo(name, defaultNumber, toUpperCase);
    }

    public String foo(String name) {
        return foo(name, defaultNumber);
    }
}

2.2: Collections functions

In 1: Collection to String, we wrote some Kotlin to turn a collection of numbers into a string, enclosed by braces. In this exercise, we just have to use on line of code to achieve the same thing using joinToString:

fun task2_2(collection: Collection<Int>): String {
    return collection.joinToString(", ", "{", "}");
}

3: Lambdas

This exercise essentially asks us to use lambdas to search for a value, in this case 42 in a list of integers. I could not find what I was looking for in the exercise file itself. For a person who is not used to Kotlin's lambda syntax this exercise might seem a little confusing. I happened to find out how to use lambdas in Kotlin from a blog post. In the beginning, I ended up with the following:

fun task3(collection: Collection<Int>): Boolean {
    return collection map { x -> x % 42 == 0} contains true
}

Here, we learn about Kotlin's new syntax for lambdas. One does not have to use braces, but they are allowed:

fun task3(collection: Collection<Int>): Boolean {
    return collection.map({ x -> x % 42 == 0}).contains(true)
}

However, a friend of mine pointed out that map is not needed. Instead, any [4] [5] is a better solution:

fun task3(collection: Collection<Int>): Boolean {
    return collection any {i -> i % 42 == 0}
}

But, one can go even further. Instead of declaring a lambda that takes in a parameter, one can use the implicit iterator created, it shown in 1 below:

fun task3(collection: Collection<Int>): Boolean {
    // ---------------------vv------------------+
    return collection any { it % 42 == 0 }  //  1
}

4: String templates

In this exercise, we need to make a regex to match the patterns in the tests. The regex for months is already made:

val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)"

All that we need to do is use this variable inside the task4() function like so:

//                    |||                                        |||
// -------------------vvv----------------------------------------vvv
fun task4(): String = """(\w*) (\w*) \((\d{2}) ${month} (\d{4})\)"""

Above, we stick in the month variable, we could have also used $month instead of ${month}. The string that we're using is triple quoted, or a multi-line string.The difference is that in a multi-line string, special characters don't need to be escaped.

5: Null safety

This exercise is meant to demonstrate Kotlin's safety features. We have to change the body of sendMessageToClient to do the following:

  • If client is not null, then we get its personalInfo
  • If personalInfo is not null, then we get the email from it
  • If both email and message are not null, then we send a message using mailer.sendMessage. Otherwise, we return.

Initially, I ended up with the following:

fun sendMessageToClient(client: Client?, message: String?, mailer: Mailer) {

    val personalInfo = client?.personalInfo  // 1
    val email = personalInfo?.email          // 2

    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    } else {
        return  // 3
    }
}

I use two statements, 1 and 2 to get the data I needed. This However, can be done in a better way:

val email = client?.personalInfo?.email

In 3, we just have an empty return statement. When a function returns nothing, it is actually returning Unit. Unit is a singleton and does not have to be return explicitly [6]. In other words, this function will work just fine:

fun sendMessageToClient(client: Client?, message: String?, mailer: Mailer) {

    val email = client?.personalInfo?.email

    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

In the end, we need email and message to use mailer.sendMessage. Even in something like Python, one would need multiple if statements to get this done, and plenty of hasattr calls.

6: Smart Casts & Recursion

In this exercise, we need to write a recursive function using Kotlin's when keyword. The basic gist of the problem is that we have:

  • An abstract class called Expr
  • A class called Num that represents a number.
  • A class called Sum that represents the sum of two Num types.

With this, we need to create a function that:

  • Returns the expression of a summation or a number. For example:
    • print(Num(2)) should return "2"
    • print(Sum(Sum(Num(1), Num(2)), Num(3))) should return "1 + 2 + 3"

Hence, if we are given a Sum object, we need to call the function again breaking the sum down into two parts, the left and the right, as shown in Sum's function definition:

class Sum(val left: Expr, val right: Expr) : Expr()

With that in mind, we arrive at the following function, which sadly is called print:

fun print(expr: Expr): String {  // 1
    when (expr) {  // 2
        is Num -> return expr.value.toString()  // 3
        is Sum -> return print(expr.left) + " + " + print(expr.right)  // 4
        else -> throw IllegalArgumentException("Unknown expression")  // 5
    }
}

In 1 we declare the function print that takes in a type of Expr and returns a String. In 2, when feels like a switch statement. If expr is of instance Num, then we return the value of expr as a String in 3.

However, if expr is of instance Sum, then we break down the expr, and call print on its left and right properties in 4.

Otherwise in 5, we throw an IllegalArgumentException error because expr is neither a Sum or Num.

In 4, we could have also used string templates instead, like so:

fun print(expr: Expr): String {
    when (expr) {
        is Num -> return expr.value.toString()
        //                vvvvvvvvvvvvvvvvvvv   vvvvvvvvvvvvvvvvvvvv
        is Sum -> return "${print(expr.left)} + ${print(expr.right)}"
        else -> throw IllegalArgumentException("Unknown expression")
    }
}

In this example, we do not need to explicitly cast expr as Num or Sum, like we have to do in Java:

(...)
if (expr instanceof Num) {
        return "" + ((Num) expr).getValue();
}
(...)

7: Data Classes

Data classes are like case classes in Scala, they're for storing data, and have a lot of useful helper functions baked in, like equals and hashCode and the obligatory toString. In this exercise, we really don't have a lot to do, we just have to read through some of the code samples. One thing I feel that the samples missed out on showing that data classes can also have optional parameters. The following code:

data class Account(val name: String, val account_id: String, val active: Boolean = true)

fun main(args: Array<String>) {
    val ac = Account("Nafiul Islam", "A2231200ODSSDF4%%32123")
    print("${ac.name}, ${ac.account_id}, ${ac.active}")
}

Outputs:

Nafiul Islam, A2231200ODSSDF4%%32123, true

8: Extension functions

The Kotlin team came up with a pretty neat euphemism for what is essentially monkey-patching. Extension functions allow you to patch a class with extra methods. For example:

fun String.lastChar() = this.charAt(this.length - 1)

fun main(args: Array<String>) {
    print("Hello World!".lastChar())  // Outputs: !
}

This is pretty neat, we can actually monkey patch stdlib classes. So, without much further ado, we need to create extension functions on Int, to allow two new constructor methods for a RationalNumber. One being, Int.r(), which allows us to create a rational number like so:

4.r()  // Outputs RationalNumber(4, 1), in essence 4/1

As well as:

Pair(10, 20) // Outputs RationalNumber(10, 20), in essence 10/20

This is one way of implementing such auxiliary constructors:

data class RationalNumber(val numerator: Int, val denominator: Int)  // 1

fun Int.r(): RationalNumber {
    return RationalNumber(this, 1)  // 2
}

fun Pair<Int, Int>.r(): RationalNumber {
    return RationalNumber(this.first, this.second)  // 3
}

In 1, we have declared our RationalNumber data class. This is already given to us in the exercise.

In 2, we return an a RationalNumber, the first parameter is this, and the second 1. Basically, creating 4/1.

In 3, we extend Pair, this is an inbuilt class from the stdlib. In this case, we're extending a pair of two Int variables. Code completion brought up first and second fields for Pair. However, the documentation did not say it had these fields:

Information on Pair

Hitting + B [7] brought this up inside Tuples.kt (which appears to be part of the stdlib):

public data class Pair<out A, out B>(
        public val first: A,  // 1
        public val second: B  // 2
) : Serializable {
    override fun toString(): String = "($first, $second)"
}

We can see that in 1 and 2, first and second is declared as variables.

9: Extending Collections

In this exercise, we have to extend collections. Following the convention of the other exercises before, we need to translate some Java code to Kotlin code. In this case, the Java code does the following:

  • Group strings present in collection by length.
  • Find the maximum size of all the groups, meaning find the group that has the most number of members. Stored in maximumSizeOfGroup.
  • Return the members of the group with the highest number of members.

After deciphering the Java version of the program in JavaCode9.java, I managed to figure out what needed to be done. Here are the results:

fun doSomethingStrangeWithCollection(collection: Collection<String>): Collection<String>? {
    val groupsByLength = collection. groupBy { s -> s.length }

    val maximumSizeOfGroup = groupsByLength. values(). map { group -> group.size }. max()

    return groupsByLength. values(). firstOrNull { group -> group.size == maximumSizeOfGroup }
}

10: Object Expressions

This exercise shows us how to modify object instances on the fly. In this case, we need to modify MouseAdapter to register the number of clicks that it receives. So, following the examples, we can see that we need to use the following syntax:

val foo: Foo = object : Foo() {
    override fun foo() {
        // Code goes here
    }
}

With that set, lets modify our own instance of MouseAdapter:

fun task10(handleMouse: (MouseListener) -> Unit): Int {
    var mouseClicks = 0

    val customAdapter : MouseAdapter = object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {  // 1
            super<MouseAdapter>.mouseClicked(e)  // 2
            mouseClicks += 1  // 3
        }
    }

    handleMouse(customAdapter)  // 4
    return mouseClicks  // 5
}

In 1 we override the function that handles mouse clicks. In 2, we make a call to the super function. When doing this, I did not know how to make this call and found it well documented in the reference [8].

In 3, we increase the number of mouse clicks in our mouseClicks variable. In 4, we register the handler and finally in 5, we return the number of mouse clicks.

Takeaway

The Kotlin Koans have a lot of material packed in. If you're coming from a language other than Java, it might be a little difficult to understand what is going on. I wish it had more language agnostic instructions, as Kotlin is targeting JavaScript platforms as well.

What I've learnt from the Introduction is that Kotlin is a ridiculously powerful language. Some of its features can even make Python sweat. The parts I liked most were lambdas, null safety features and extension functions.

In total there are six parts to this series. We've just completed the first. I just can't wait for more :)

Acknowledgements

A big thanks to the following people:

  • Taro Nagasawa
  • Keiron Pizzey
  • Ilter Canberk
  • Sahib Bin Mahboob
  • Jeong Min-Lee
  • Ashwini Chaudhury
  • Hadi Hariri

[1]If I've made a mistake in this list, or if you have any other suggestions that I've missed out, please do not hesitate to add.
[2]Hadi Hariri is an evangelist at Jetbrains.
[3]Dash doctests are also supported by other tools on different platforms. Check out Zeal.
[4]有難うございます。(Thank you very much) to Taro Nagasawa for pointing this out to me :)
[5]
Implicit iterators talked about towards the end of "Higher Order Functions"
[6]
Andrey Breslav, the lead designer of the language commented on Unit
[7]For Win/Linux, one can use CTRL + B, in essence "Go to Source".
[8]In the section called "Overriding Rules" http://kotlinlang.org/docs/reference/classes.html#overriding-rules