Press "Enter" to skip to content

Shooting Yourself In The Foot with Kotlin Type-Inference and Lambda Expressions

Kotlin takes Type-Inference to the next level (at least in comparison to Java), which is great, but there’re scenarios, in which it can backfire.

The Riddle

fun foo(int: Int?) = {
    println(int)
}

fun main(args: Array<String>) {
    listOf(42).forEach { foo(it) }
}

And the question is: what does the main() method print and why it’s not 42? Assume that the method gets executed.

You can find the answer at the very end of the article.

Explanation

In Kotlin, we’ve got not only a local variable type inference (which is also coming to Java) but also the return value type inference when writing single-expression methods.

Which means that if we write a method:

fun foo() : String  {
    return "42"
}

We can rewrite it using the single-expression method syntax and ignore the explicit type declaration and the return keyword:

fun foo() = "42"

So, if we have a method:

fun foo(int: Int?) : Unit {
    println(int)
    return Unit
}

we could get rid of the return type declaration, as well as the explicit return statement by leveraging single-expression method syntax, right? but we already know that the following doesn’t work:

fun foo(int: Int?) = {
    println(int)
}

The devil’s in the details, but everything becomes crystal clear if we specify the return type explicitly:

fun foo(int: Int?) : () -> Unit = {
    println(int)
}

If we look closely, it can be seen that we mixed two approaches here and defined a method that doesn’t return Unit but a () -> Unit itself – which is simply an action that doesn’t accept any parameters and doesn’t return anything – which is semantically equivalent to returning a java.lang.Runnable instance.

This is because Kotlin utilizes curly braces not only for defining classes/methods but also for defining Lambda Expressions and { println(42) } is a valid Lambda Expression declaration:

val foo: () -> Unit = { println(42) }

So, our original example is simply a Higher-Order Function accepting an Int parameter and returning a function that prints it – when we return it in the forEach(), the return value just gets ignored.

So, if we want to fix our example, we have two ways to go.

Explicitly call invoke() on the returned function:

listOf(42)
  .forEach { foo(it).invoke() }

or simply define the method accurately by removing the “=” sign:

fun foo(int: Int?) {
    println(int)
}

or by leveraging the single-expression method syntax:

fun foo(int: Int?) = println(int)

That’s why I encourage my fellow team members to declare return types explicitly.

You can find code snippets on GitHub.

Answer

The method prints nothing.




If you enjoyed the content, consider supporting the site: