Personal Blog by Galen Wong

Kotlin Learning Notes

The Traps and Pitfalls

2019-12-3110 min read

This is a blog post will act as a note as for my Kotlin learning. I will document some pitfall that I encountered while learning the language and present it in a format similar to evil interview questions because I like torturing other people with interview questions.

Lambda and Anonymous Functions (A.F.)

return from Lambda and A.F.

Take a look at the following code snippet. Predict its output to the console.

fun main(args: Array<String>) {
    val arr = arrayOf(1, 2, 3, 4, 5)
    println("stopAt3WithLambda")
    stopAt3WithLambda(arr)
    println("stopAt3WithAF")
    stopAt3WithAF(arr)
}

fun stopAt3WithLambda(args: Array<Int>) {
    args.forEach { k -> 
        println("this is $k")
        if (k == 3) return
    }
    println("finished, returning")
}

fun stopAt3WithAF(args: Array<Int>) {
    args.forEach(fun (k: Int) {
        println("this is $k")
        if (k == 3) return      
    })
    println("finished, returning")
}
Answer

The code block outputs the following to the console:

stopAt3WithLambda
this is 1
this is 2
this is 3
stopAt3WithAF
this is 1
this is 2
this is 3
this is 4
this is 5
finished, returning

Explanation

According the docs, a “qualified return” allows us to “return from an outer function”. I personally find it hard to understand the sentence when I first read it. What is a “qualified” return? Why an “outer” function? Doesn’t the return keyword just return from the “current” function?

To understand the statement, I interpret the terms as such:

  • A “qualified” return is an action of return through the

explicit return statement with the return keyword. Meaning

return 1

is a qualified return. Another type of return is the implicit return from a lambda. Meaning, the last expression of the body of the lambda.

{ a, b ->
  println("a: $a b: $b")
  a + b
}

The given lambda expression returns a + b implicitly, and this is not a qualified return.

  • “Function” should be understood as a code block that can be executed or invoked that is declared by the fun keyword. A lambda is also a code block that is executable but it is not a “function” in this context. Therefore, in the statement, “returning from an outer function” means returning from a code block declared through the fun keyword.

With such definition, this statement describes the behavior of qualified return in a lambda expression correctly. In the given example above, the return keyword in the lambda is returning from stopAt3WithLambda instead of the lambda expression. That’s why "finished, returning" is not printed.

In stopAt3WithAF, however, the return keyword in the A.F. is returning from the anonymous function that is declared through a fun keyword. This is not a contradiction to the statement if we interpret it according to the definitions laid out above.

The property introduces more interesting questions.

  1. What happens if we nest lambdas with inner qualified return?
  2. What if we pass a lambda with qualified return as an argument to a function? Will the behavior differ if we pass an A.F. instead?

Nested Lambda with Qualified Return

According to the interpretation of the statement, I anticipate that the return to escape the nested lambda and return from the outer function with fun keyword.

To validate the theory, we change our stopAt3 to traverse a 2D array instead. We have the following code snippets.

fun main(args: Array<String>) {  
    val matrix = arrayOf(
    	arrayOf(1, 2, 3, 4, 5),
        arrayOf(1, 2, 3, 4, 5)
    )
    
    stopAt3Nested(matrix)
}

fun stopAt3Nested(matrix: Array<Array<Int>>) {
    matrix.forEach { row ->
        row.forEach {k ->
        	println("this is $k")
            if (k == 3) return 
        }
    }
    println("finished, returning")
}

As expected, the output is

this is 1
this is 2
this is 3

Meaning we returned from stopAt3Nested directly, without printing "finished returning".

Passing Lambda vs A.F. as Argument

What if we pass a lambda with qualified return as an

argument to a function? Will the behavior differ if we pass an A.F. instead?

The reason why I asked this question is that if the return statement in lambda or A.F. will return from the current scope where they are declared or within in the scope that they are called (the function that accepts the lambda or A.F. as an argument). I realized that the answer is clearly stated in the given example.

In our example, we passed lambda and A.F. to the function of Array<T>.forEach, and we see the difference in their behavior. Therefore, this provides us with a way to think about the return statement. It jumps to the end of the closest function “frame” delimited by the fun keyword.

fun f() {
  fun g() {
    fun h() {
      return 1 // jumps to the end of h, the closest function "frame"
    }
  }
}

Another way to think about this is that return is “statically scoped” instead of dynamically scoped. The target function to escape from is resolved at compile time statically, instead of at run time dynamically.

Moral of the story: I should rethink about the term lambda. Computer Scientists could have called lambda just a shorthand for an anonymous function (in Kotlin at least), but they did not for a reason. Lambdas and functions are different. The property of jumping out of the closest function frame of a qualified return in lambda really makes lambda different from a function. It seems to open up doors to very interesting programming patterns but I can’t come up with anything off the top of my head.

The Implicit it in Lambda

This is super weird to me at first but I guess it make sense as a short hand.

What is the output if we executed the following function?

fun whatIsIt() {
  val it = 2
  val myEvilLambda = {
    println("it in lambda: $it")
  }
  myEvilLambda(3)
}
Answer

No, it is not 3 since this does not compile. For the reason that the type of it cannot be inferred at declaration. Therefore, it is resolved to the it variable declared in the scope of whatIsIt. Therefore, myEvilLambda has the type of Kotlin.Function0, which means the lambda does not accept any arguments. To fix the problem, we can declare the parameter of it with an explicit type.

val myEvilLambda = { it:Int ->  
  println("it in lambda: $it")
}

Well, this loses the point of the implicit it shorthand.

The it implicit keyword really shines when you are passing a lambda to another function as an argument, since the function that accepts the lambda already have a type declared for the lambda. For example, in Android, when you add an listener:

myButton.setOnClickListener {
  val randNum = Random.nextInt(0, 100)
  it.setText("$randNum")
}

Or what the people in JavaScript lands call the functional programming style of map, reduce, filter, but instead in Kotlin they are flatMap, fold, and filter.

intArray.filter { it > 0 }

Trailing Lambda Shorthand

In the previous example of Array.filter and setOnClickListener, we see that we can pass lambda to a function omitting the parenthesis. This applies to any function with the last argument being a function type (here function type is used to refer to both lambda and functions).

An example from the documentation is

val product = items.fold(1) { acc, e -> acc * e }

Wow, this feels like functional programming a lot, where items.fold(1) returns a function as well. No, this is only a visual trick to make you think that this is a higher order function when it is actually not.

lateinit Variables

I understand the motivation behind lateinit, especially in the context of UI development where some variables are only initialized in the lifecycle methods.

However, my first thought looking at this feature is that it is quite dangerous for a compiled language. It is like the programmer tries to make a promise to the program like “I will definitely gives the variable a value before I use it. I promise”. We all know that human is never reliable. What happens if we access it without initializing it? Well, an exception is thrown. I naively thought that the program will actually not check if it is initialized first since performance is better without the null check, but yes that was way too naive. Null check is still done in run time before using lateinit variables. It is not too much speed to sacrifice for safety. You can just view it as a nullable type. However, it is stricter since it cannot be null. It either has a value or does not have a value at all.

Inflation

In Android development, the term is used a lot. It means “instantiating a layout XML file into its corresponding View objects” (source: LayoutInflator). I understand it as parsing the XML file and generating the view hierarchy in memory. To draw analogy from the web dev domain, it would be to parse the HTML file and generating the actual DOM tree.

Default Parameters with Inheritance

Read the following code snippet. Predict its output to the console.

fun main(args: Array<String>) {  
	val a = A()
    val b = B()
    println(a.method())
    println(b.method())
}

open class A {
    open fun method(i: Int = 1): Int {
        return i
    }
}

class B: A() {
    override fun method(i: Int = 2): Int {
        return i
    }
}
Answer

No, there is no output since this code does not compile. The reason can be found in the Kotlin docs. Here I quote it.

Overriding methods always use the same default parameter values as the base method. When overriding a method with default parameter values, the default parameter values must be omitted from the signature.

The compiler error message for this case would be

An overriding function is not allowed to specify default values for its parameters

Discussion

Initially, I cannot understand the intention of such language design decision. It is not difficult to implement such language feature. The reason for such design is perfectly answered by this Stack Overflow answer. The following quote from the answer explain why overriding parent classes’ default parameters is bad:

Callers would not know what the default value is unless they were aware of which implementation they were using, which is of course highly undesirable.

What does it look like in code? Let’s assume that the in another parallel universe the Kotlin compiler allow us to override the parent classes’ default parameters. Therefore the code above compiles. Let’s say that we declare a function that takes in an object of class A as parameter.

fun processClassA(obj: A) {
  return obj.method() * 2
}

When the programmer want to use A.method and its default parameters, it has to know which implementation of A is being passed in. The reason for that is that we can also pass an instance of B into processClassA by the principle of OOP. That’s why such behavior is undesirable.

Therefore, letting the parent class dictates the default parameters is the best choice to make so that programmer can reason about their program easier.

Default Parameters Evaluation

Predict the output to the console of the following code snippet.

fun main(args: Array<String>) {  
    for (_i in 1..10) {
        whatIsGlobal()
    }
}

var global = 0

fun getAndIncGlobal(): Int {
    global++
    return global
}

fun whatIsGlobal(g: Int = getAndIncGlobal()) {
    println(g)
}
Answer
1
2
3
4
5
6
7
8
9
10

This proves that the default parameter expression is evaluated at call time instead of compile time, unlike Python.