Why I Like Kotlin
For the last couple of months I’ve been slowly going from Python to Kotlin. Kotlin is a statically typed language running on JVM. During the last years I was rather avoiding all the JVM stuff. Now I’m slowly starting to appreciate this.
I always disliked Java for the huge memory consumption, garbage collector, the terrible inconsistency and quite stupid design decisions. Then I slowly switched to Python. It was great at the beginning. However, when going deeper and deeper, and the projects were getting bigger and bigger, it turned out that it’s just slowing down the whole development.
Kotlin
For me Kotlin is fixing all the problems I had with Java. Among other things Kotlin has:
- operator overloading
- null safety
- multiline strings
- data classes
- extension functions
- no primitive types
- functions default arguments
- functions named arguments
Operator Overloading
Due to a very short sighted decision, Java has no operator overloading. Generally in different languages we have three attitudes to this:
- no overloading (like in Java)
- overloading only a specific set of operators (C++, Kotlin)
- creating custom operators with custom names (Haskell)
I found a citation of Joshua Bloch saying (unfortunately I cannot find the source now):
There is no operator overloading in Java because I have seen too many programmers who did that wrong.
I’m sure it’s not only a wrong argument, it’s also a stupid one. We can do all things in a wrong way. Does it mean that we should prohibit using everything? Operators are just normal functions with some syntactic sugar about calling them.
A good example of no operator overloading problem is this code in Java:
double a = 1.0;
double b = 2.0;
double c = Math.sqrt(a*a + b*b)
For doubles it works. If the double accuracy is not enough, then we should use a type like BigDecimal
. Unfortunately changing types doesn’t work. All the code must change:
BigDecimal a = BigDecimal.valueOf(1.0);
BigDecimal b = BigDecimal.valueOf(2.0);
double c = Math.sqrt(
a.multiply(a).add(b.multiply(b)).doubleValue()
);
Much more readable, right? This is the problem with the lack of operator overloading.
The argument “because people can do it in a wrong way” is also stupid because I can implement the function operator+
in the same bad way as I can implement the function add
. The name doesn’t matter for a bad implementation, but can greatly simplify the code.
Multiline Strings
One of the bad design decisions in Java was that there was no way to have a string with multiple lines. This makes the code like this quite unmaintainable:
String str = "This is an example\n"
+ "of a multiline\n"
+ "string in Java";
In Kotlin there is simply:
val str: String = """This is an example
of a multiline
string in Kotlin"""
Extension Functions
Extension functions are quite a nice syntactic sugar. The first extension function I’ve created was to simplify this code:
LocalDate.parse("2018-01-01")
It was used in tests over and over again. The simple extension function was:
fun String.asDate(): LocalDate {
return LocalDate.parse(this)
}
Now with the extension function the code is:
"2018-01-01".asDate()
The good thing about this mechanism is that it doesn’t change the String
class, which would be rather difficult in Java.
It creates an external static Java function:
final class Utils {
public staic LocalDate asDate(String x) {
return LocalDate.parse(x)
}
}
and the kotlin code:
val x = "2018-01-01".asDate()
is compiled to:
LocalDate x = Utils.asDate("2018-01-01")
The extension function also doesn’t pollute the global space with such a function, to use this function, you need to import it.
Lambdas
Lambdas are unnamed functions which can be used as normal objects, e.g. can be passed to other functions. They can also be executed.
For instance, the below function runs the lambda passed as the argument and returns the time the function run.
fun benchmarkFunction(function: () -> Unit): Long {
val startTime = System.currentTimeMillis()
function()
val stopTime = System.currentTimeMillis()
return stopTime - startTime
}
This can be used with the great syntactic sugar: if the last argument is of the type of () -> Unit
then we can pass the block:
val elapsedTime = benchmarkFunction {
doSomethingExpensive()
}
The benchmarkFunction
can also be called with multiple arguments, the general rule is that the function must have a function ()->Unit
as the last argument:
fun benchmarkFunction(times: Int, function: () -> Unit): Long {
val startTime = System.currentTimeMillis()
repeat(times, function)
val stopTime = System.currentTimeMillis()
return stopTime - startTime
}
val elapsedTime = benchmarkFunction(repeat=10) {
doSomethingExpensive()
}
Huge Java Libraries Collection
Kotlin is just another language running on the jvm so it has access to all the Java libraries, including the whole jdbc stuff, frameworks, testing, and all the things you can use in a normal Java application.
Default Arguments
Default arguments allow for calling a function with less arguments than declared. The below function calls will be made passing the same arguments to the function.
fun test(a: int, b: int=10)
test(1, 10)
test(1)
Named Arguments
Generally, when calling a function you need to provide the arguments in the proper order. The named arguments are great for calling a function with multiple default arguments when you want to omit them. This also allows to call them in a different order.
For instance, this function
fun test(a: int=1, b: int=2, c: int=3)
can be called as:
test()
which will pass a=1, b=2, c=3
.
The named arguments allow me to set e.g. only c=5
with this:
test(c=5)
which means that the function will be called with:
test(1, 2, 5)
Data Classes
A feature also implemented in the Python 3.7.
When you implement a class in Java, to have good behavior you need to implement a couple of functions like getters, setters, equals, hashCode etc. I usually used the implementations generated by an IDE.
With the data classes you can generate a whole class with:
data class User(
name: string,
email: string
)
This will create a class which will have automatically generated functions:
equals()
hashCode()
toString()
copy()
getName()
getEmail()
setName()
setEmail()
The objects of the data classes are immutable, which greatly simplifies testing and thinking about the data flow.
There are also componentN
functions. For a class with two arguments there will be two functions generated: component1
and component2
. They will be used in the destructuring declaration like:
val user = User("simon", "simon@example.com")
val (name, email) = user
Great IDE Support
With the IntelliJ editor programming in Kotlin is a great experience. This is not so surprising as the language is made by the same company. I don’t know how it looks like in other languages.
What’s Next?
I’m slowly mentally leaving Python and all the dynamic stuff. There is just too much overhead on using the libraries and on the program execution speed. Yes, writing the code too. The IDEs cannot give me proper hints in all the cases, automated refactoring simply doesn’t work. What’s more Python 3 will not fix this, the problem is somewhere else.