Advanced Kotlin tips: tail recursion, sealed classes, local, infix and inline functions and more…

Tips on writing good Kotlin code and make good use of what the language has to offer

Mostafa Gazar
AndroidPub

--

In a previous post I covered some essential Kotlin tips like Singleton, Utility Functions, group Object Initialization, etc…, this time I will cover some more advanced tips.

Let us make a mess

Local functions

Local functions are good for code reuse, just be careful not to overuse them to avoid confusion.

fun foo(a: Int) {
fun local(b: Int) {
return a + b
}
return local(1)
}

Infix functions

Infix functions are good for readability, because it allows typing something like "test" foo "x" for example, pretty cool eh!

infix fun String.foo(s: String) {
...
}
// Call extension function.
"test".foo("x")
// Or call extension function using infix notation.
"test" foo "x"

Infix functions must have a single parameter.

Inline functions

A lambda expression in Kotlin is translated to Java anonymous classes in Java 6 or 7, that is an overhead. Lambda calls are affecting the call stack which has a performance impact.

inline functions can be used to flat out calls instead of invoking another method call and adding that to the call stack. So it makes sense to use inline functions when we pass in the lambdas.

inline fun callBlock(block: () -> Unit) {
println("Before calling block")
block()
println("After calling block")
}

When we call callBlock it gets translated to something like the following:

callBlock { println("The block operation") }// Rough java bytecode
String var1 = "Before calling block";
System.out.println(var1)
String var2 = "The block operation";
System.out.println(var2);
var1 = "After calling block";
System.out.println(var1);

vs. the following if the function was not marked as inline

callBlock { println("The block operation") }// Rough java bytecode
callBlock((Functinos0)null.INSTANCE);

You have to be careful with inline functions though because it literary copies the method content where it is called and if the body of the functions is too large you really do not want to do this.

Knowing that, the following obviously will not make any sense because it has zero effect.

inline fun foo(noinline block: () -> Unit) {// Single lambda marked as noinlineinline fun foo() { // No lambdas

tail recursion

By using tailrec we let the compiler know that it can replace the method call with a for loop or goto statement.

You can only use it if the last call in a function is only calling itself and only itself.

tailrec fun findFixPoint(x: Double = 1.0): Double         
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

Sealed classes

According to Kotlin reference we should use sealed classes for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type.

In other words, they are good when returning different but related types.

sealed class Response 
data class Success(val content: String) : Response()
data class Error(val code: Int, val message: String) : Response()
fun getUrlPage(url: String) : Response {
val valid = // Some logic here!
if (valid) {
return Success("Content")
else {
return Error(404, "Not found")
}
}
// Here is the beauty of it.
val
response = getUrlPage("/")
when (response) {
is Success -> println(response.content)
is Error -> println(response.message)
}

Sealed classes must be defined in a single file.

Some more small tips just for you!

Local return

They are mainly helpful with lambdas, but let us explain it with a basic code example. What do you think the first return in the following code does?

fun foo(list: List<String>): Boolean {
list.forEach {
if (...) {// Some condition.
return true
}
}
return false
}

It gets foo to return true. You can also choose to only return from the forEach scope.

fun foo(list: List<String>): Boolean {
list.forEach {
if (...) {// Some condition.
return@forEach // Just like calling break
}
}
return false
}

If that does not make much sense, check out the following code:

fun foo() {
Observable.just(1)
.map{ intValue ->
return@map intValue.toString()
}
...
}

If we used return in the above code snippet, it would return to foo which does not make much sense here. return@map though returns result of the map function which is the intended behaviour.

Operator overloading

Use operator to override supported operators.

operator fun plus(time: Time) {
...
}
// This will allow the following statement.
time1 + time2

Be careful not to overuse operator overloading, it does not make sense to use it most of the time. Check out the list of the conventions that regulate operator overloading for different operators.

Lambda extensions

They are just nicer to read and type, just like markup.

class Status(var code: Int, var description: String)fun status(status: Status.() -> Unit) {}// This will allow the following statement
status {
code = 404
description = "Not found"
}

lateinit

A lateinit property will throw an exception of type UninitializedPropertyAccessException if there was an attempt to use it before it got initialized.

Companion Objects

Companion Objects are the closest thing to java static methods.

class MyClass {
@Jvmstatic
companion object Factory {
fun create(): MyClass = MyClass()
}
}

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = MyClass.create()

I would recommend using extensions though with @file:JvmName("SomethingUtil") and @file:JvmMultifileClass when you have multiple kotlin files that you want to merge in on java util class.

If you enjoyed reading this story, please 👏 and share it to help others find it! And follow me on Twitter to get notified when I publish a new story.

Call To Action

You just need to practice, practice, practice… what you learnt in this post and in the previous one, make some mistakes along the way and have fun.

--

--