Kotlin Discovered: Functions
You never touched Groovy, nor did you jump on the Scala train. Clojure never attracted you; and you heard about Ceylon long after the language had already died. You are one of those old-fashioned Java folks! But now, after all those years, you want to join the cool Kotlin kids. So, where to start? Let’s discover the language together by decompiling it to Java code. Today: Functions!
Today: Functions
When you think about programming language, the first thing you consider is it uses of variables and functions. Though Kotlin is an object-oriented like Java, it dabbles in the functional programming world as well. So let’s start with that. As Kotlin functions can be declared at the top level in a file, it’s as easy as creating a "Test.kt" file and merely write:
fun add(a: Int, b: Int): Int {
return a + b
}
If a function consists of one line, i.e. a 'single expression', you could even omit the return
keyword and the return type:
fun add(a: Int, b: Int) = a + b
Now, let’s see the compiled code, shall we:
public final class TestKt {
public static final int add(int a, int b) {
return a + b;
}
}
That was quite easy, wasn’t it? Kotlin let us do more awesome stuff. What about nesting one function in another?
fun addPlusTwo(a: Int, b: Int): Int {
fun addTwo(a: Int) = a + 2
return addTwo(a + b)
}
Short, concise and fun. Just as we like it! The compiled Java code on the other hand… That’s different![1]:
final class TestKt$addPlusTwo$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function1<Integer> {
public Integer invoke(Integer a) {
return a + 2;
}
}
public final class TestKt {
public static final int addWithNestedFunction(int a, int b) {
var $fun$addTwo$1 = new TestKt$addPlusTwo$1();
return $fun$addTwo$1.invoke(a + b);
}
}
As you can see, nested functions do not really exist at JVM level.
The compiler simply creates a new class to meet the need!
This additional class implements a Function1
interface, which contains an invoke
method where the actual calculation is performed.
To make this work for more than one argument, Kotlin provides Function0 till Function22 classes at runtime[2].
Each function takes the number of arguments as input and returns one result.
So Function2
would take two inputs and return one output.
Naively thinking, coming from a Java backend, you could imagine yourself writing a higher-ordered function with these classes:
fun addWithCustomImpl(a: Int, b: Int, add: Function2<Int, Int, Int>) = add(a, b)
Luckily enough, Kotlin provides syntactic sugar to write above code a little bit nicer:
fun addWithCustomImpl(a: Int, b: Int, add: (Int, Int) -> Int) = add(a, b)
However, both examples do compile! Looking at the compiled source code, I think you can imagine why:
public static final int addWithCustomImpl(int a, int b, @NotNull Function2 add) {
Intrinsics.checkNotNullParameter(add, "add");
return ((Number)add.invoke(a, b)).intValue();
}
What’s funny though, there is no extra class needed in this situation.
Since the Function2
is just a regular Java functional interface, the compiler uses it as such!
There is one more thing that requires special attention. How many times have you used an API that was just missing that one method you needed? Kotlin offers the ability to add functions to existing objects, called extension functions:
fun String.isYesOrNo() = this.equals("yes", ignoreCase = true)
val example = "YES".isYesOrNo() // usage
Does Kotlin rewrite objects to make this work? Well look for yourself:
public static final boolean isYesOrNo(@NotNull String $this$isYesOrNo) {
Intrinsics.checkNotNullParameter($this$isYesOrNo, "$this$isYesOrNo");
return StringsKt.equals($this$isYesOrNo, "yes", true);
}
private static final boolean example = isYesOrNo("YES"); // usage
Nope, there is no magic involved at all… The compiler creates a normal static function and calls it. So extension functions are nothing more than syntactic sugar!
Well, that’s enough for one day. Stay tuned for more!
Function1
and Function2
are very similar to Java’s Function and BiFunction interfaces!