The character of Kotlin
Article originally published on blog.kotlin-academy.com
I’ve just finished Seven Languages in Seven Weeks by Bruce Tate and I loved it! Even though I had some experiences with most of the described languages, I really enjoyed the way he presented characteristics of languages and their influence on the final way a language is used.
This is why I decided to write one, additional chapter. It will be about the language I know really well and I understand it’s strengths and flaws: Kotlin.
The purpose of this article is not to teach the language, but to show the character of Kotlin instead. You don’t need to understand everything. Instead, concentrate on thinking how presented features would influence the way you are programming.
The very typical Kotlin trait is that it isn’t really introducing anything new into programming languages family. Instead it uses everything that was already available in a really awesome way. Think of Iron Man. Tony Stark made Iron Man using simple electronic elements. He does not have any real superpowers like Superman or Flash. It might look like a weakness, but it is a great advantage in the long term. We will talk about it later. Let’s start with basics.
Basics
We normally do Kotlin programming in some IDE like IDEA IntelliJ, Android Studio or CLion (Kotlin/Native). Here we will start from the command line to show Kotlin in a simpler context. Once you have Kotlin installed, start REPL (interactive environment) using:
Let’s try some numbers:
>>> 1
1
>>> 1 + 2
3
>>> 1.0 + 2
3.0
Simple math works. This is Kotlin/JVM that runs on Java Virtual Machine. Integers are primitives in Java. What about Kotlin? Let’s check out type of number:
>>> 1::class
class kotlin.Int
>>> 1.0::class
class kotlin.Double
They are both objects! Actually, everything in Kotlin is an object. What about Java? Kotlin is fully inter-operable with Java, but we can see that above types are Kotlin types. The reason is that some build-in Kotlin types cover Java types. We can see actual Java type using java
property of Class
or javaClass
property of every object:
>>> 1.0::class.java
double
>>> 1.0.javaClass
double
It is double
! double
is primitive in Java. How is that possible? Kotlin uses optimizations to use primitives instead of objects when no object-oriented features are used. This happens under the hood and it does not influence developer at all. If we need to use double
like an object then there will be Double
used instead. From our perspective, we can still say that everything is an object. Let’s define some properties:
>>> val a = 1
This is read-only property. We can also define read-write property using var:
>>> var a = 1
Note that there is no type specified. Don’t get mistaken, Kotlin is strongly and statically typed language. Type of property was just inferred from type of assigned value:
>>> ::a.returnType
kotlin.Int
Enough math, let’s get to some more advanced features.
Safety
The reason why Iron Man was made was that police nor army couldn’t rescue Tony Stark from terrorists. Tony made Iron Man to improve his own safety and to give himself better possibilities. He also made himself much more famous and popular. Similarly, Kotlin was made by JetBrains. This is the company that makes most popular programming IDEs. All their tools were initially developed in Java, but they were experiencing flaws of this language. They started experimenting with other languages like Scala or Groovy, but they were not satisfied. They finally decided to make their own language and it was important to them that this language should provide maximal security (so they won’t have errors in their products) and scalability. Kotlin is also giving JetBrains a lot of additional popularity. They were known everywhere, but now when people can use their awesome language, JetBrains is even cooler and greater for them. (This story is the huge simplification. Listen to this podcast to hear more accurate version.)
Kotlin highly improves safety over Java. Properties must be initialized:
>>> var a: String
error: property must be initialized or be abstract
Types by default are not nullable:
>>> var a: String = null
error: null can not be a value of a non-null type String
We say that type is nullable using ?
:
>>> var a: String? = null
Although nullable type cannot be used explicitly:
>>> a.length
error: only safe (?.) or non-null asserted (!!.) calls are allowed
on a nullable receiver of type String?
Nullable type is like option type known from other languages like Scala. We need to unpack it before usage. We can use safe call that works like normal call when the property is not null
. If the property is null
, it doesn’t call method but it returns null
instead:
>>> a = null
>>> a?.length
null
>>> a = "AAA"
>>> a?.length
3
We can also use unsafe call that throws exception when property is null;
>>> a = null
>>> a!!.length
kotlin.KotlinNullPointerException
Unsafe call is bad practice and should be used as rarely as possible. Very rarely you can find it in Kotlin projects. This is huge difference comparing to Java, where every call is unsafe call.
Smart
Cool Iron Man feature is that his suit is really smart. It analyzes a situation and informs Tony about dangers. It can also repair itself. Kotlin is really smart too, and it helps developers a lot.
One example of such behavior is smart casting. When we check if property is not null, we can use it as if it is not. We will operate on files to show more advanced features. I suggest using IDEA IntelliJ (check out how to get started). Alternatively, you can check it all out on online REPL. Let’s look at this example:
fun smartCastingExample(str: String?) {
if(str != null)
print("Length is " + str.length)
}
You can see that str
is used explicitly (without unsafe or safe call). This is because in the scope of the check if(str != null)
it is cast from String?
to String
. It also works if we exit the function in the opposite check:
fun smartCastingExample(str: String?) {
if(str == null)
return
print("Length is " + str.length)
}
It doesn’t only work for nullability. We can smart cast also to type:
fun smartCastingExample(any: Any?) {
if(any is String)
print("String with length " + any.length)
}
Kotlin is highly supported in IDEA IntelliJ, Android Studio or CLion. On these IDEs, you have lots of tips, suggestions, and support. Here is an example where imperative collection processing typical to Java is replaced to declarative one that is typical to Kotlin. Note that whole transition is suggested and made by environment:
Minimal
Tony Stark does not wear full Iron Man suit when he doesn’t need to. He normally used the car or just smaller parts.
Part of Kotlin philosophy is that simple things should be simple. This is Hello World in Kotlin:
fun main(args: Array<String>) {
print("Hello, World")
}
It is just a single function that prints text. Rest of common Kotlin cases are simple too. When we don’t need whole function body, we can just use single expression instead:
fun add(a: Int, b: Int) = a + b
We will see such minimalistic style in next parts over and over again.
Flexibility
Comparing to Superman, Iron Man miss some important features. Like lasers in eyes. Superman was born with them and he uses them whenever he needs. Tony Stark hasn’t installed lasers in Iron Man probably because he hasn’t seen that need for them is urgent enough. The important point is that he can still easily add this feature. Actually, every single Iron Man user can add them. But they can also add other features instead that might give better impact in the lower cost. This is a great power of flexibility. Let’s see it in practice. Most modern languages provide some collection literals. In Python, Ruby or Haskell you can define list this way: [1,2,3]
. Kotlin hasn’t included such collection literals, but instead, it allows top-level functions (functions that can be used everywhere) and Kotlin standard library gives top-level functions that can be used to create collections:
>>> listOf(1,2,3)
[1, 2, 3]
>>> setOf(1,2,3)
[1, 2, 3]
>>> mapOf(1 to "A", 2 to "B", 3 to "C")
{1=A, 2=B, 3=C}
Why is that important? When language provides collection literal, it defines how users will use collections. All collections have some characteristics. One big discussion is if we should prefer mutable or immutable lists. Mutable is more efficient, but immutable are much more thread safe. There are many discussions and opinions on this subject. Having this in mind, would you choose that list literal should produce a mutable or immutable list? Either way, you would influence the way people use language because they would prefer to use collection literal. Kotlin leaves this freedom. listOf
, setOf
, and mapOf
produce immutable collections:
>>> var list = listOf(1,2,3)
>>> list.add(4)
error: unresolved reference: add
list.add(4)
^
>>> list + 4
[1, 2, 3, 4]
Although we can easily make mutable collections using mutableListOf
, mutableSetOf
and mutableMapOf
:
>>> mutableListOf(1,2,3)
[1, 2, 3]
>>> mutableSetOf(1,2,3)
[1, 2, 3]
>>> mutableMapOf(1 to "A", 2 to "B", 3 to "C")
{1=A, 2=B, 3=C}
Note, that everyone can define it’s own collection and define top-level function to create it:
fun <T> specialListOf(vararg a: T): SpecialList<T> {
// Code
}
Above you can notice that I used generic type parameter
T
. Don’t worry. This way we say that we need to pass set of elements of the same type to make collection of this type.
Thanks to the fact that Kotlin uses basic features instead of built in literals, outside libraries have the same power as Kotlin standard library. Another great feature, that highly liberates libraries and developers, is called extension function. Basically, we can define function that acts as if it is method:
>>> fun Int.double() = this * 2
>>> 2.double()
4
Note that it doesn’t really add a method to any class. Extension functions are just functions that are invoked in this specific way. This feature might look simple, but it is really powerful. For instance, Kotlin like other modern languages provides functions for collection processing:
class Person(val name: String, val surname: String)
val avengers = listOf(
Person("Tony", "Stark"),
Person("Steve", "Rogers"),
Person("Bruce", "Banner"),
Person("Thor", "")
)
val list = avengers
.filter { it.surname.isNotBlank() }
.sortedWith(compareBy({ it.surname }, { it.name }))
.joinToString { "${it.name} ${it.surname}" }
print(list) // Prints: Bruce Banner, Steve Rogers, Tony Stark
The great advantage of Kotlin is that these and similar functions are defined as an extension functions. You can see, for instance, filter
implementation:
inline fun <T> Iterable<T>.filter(
predicate: (T) -> Boolean
): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(
destination: C, predicate: (T) -> Boolean
): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
If it wouldn’t be implemented, you could define this function yourself. If you need some additional function for collection processing, you can easily define it. Developers do that often. You can find, for example, lots of libraries that define extension functions to Android. They make Android development easier. Another result of this decision is that there is a huge number of collection processing methods you can use:
Another important fact you can notice is that filter
is not an extension function to List
type. It is extending Iterable
interface:
public interface Iterable<out T> {
public operator fun iterator(): Iterator<T>
}
You can easily define your own collection class that implements Iterable
, and it would get these collection processing methods on it for free. Even String
implements it. This is why we can use all collection processing methods on String
as well:
>>> "I like cake!".map { it.toLowerCase() }.filter { it in 'a'..'z' }.joinToString(separator = "")
ilikecake
Summary of day 1
Tony Stark wasn’t born with superpowers and he hasn’t been bitten by any radioactive spider. He has made Iron Man using his incredible knowledge and intelligence. Similarly, JetBrains, company that makes great programming environments for all languages, have used their knowledge to make the incredible programming language. They haven’t introduced any new feature into programming world, but instead a really well-designed language that uses advantages of whole knowledge about programming languages to maximize developer productivity and projects quality.
A great example of such smart designing can be found in the story about tuples. When Kotlin was still in Beta, tuples were supported. Although when Kotlin team was analyzing how tuples usage influences how programs are developed in general, they find out that this influence is not necessarily positive. This is why tuples were not released. Kotlin has data
annotation that can be used instead. It is more universal and Kotlin team was sure that it has positive influence on general language usage. Tuples are still discussed and probably they will be supported natively one day, but before this will happen, Kotlin team and community need to be sure that this is a good decision.
This says a lot about Kotlin. It is not a sink of ideas, but instead, it is really smartly designed language. It has a minimal set of powerful features that gives awesome possibilities. It will be especially visible on day 3 when we will explain Kotlin coroutines. In general, Kotlin gives much more freedom and leaves more space for community creativeness.
Self-study
If you want to learn some more Kotlin, I suggest you checking out Kotlin documentation and Kotlin Koans. If you want to learn Kotlin in Android, check out this book.
Next
This is not the end of Kotlin great features. This is just week 1. Iron Man design gives him some other superpowers over rest of superheroes. Some of them are not easy to notice, but they are all important. I will describe them soon in other articles.
I would like to thank Dave Leeds, Adrien Pessu and Andreas Neumann for reviewing this article.
If you need help with learning Kotlin, remember that we give consultations.
To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter and observe Twitter.