Exit Full View

Kotlin this is unclear

Seeing incorrect usage of Kotlin's let() function made me think about the other functions in Kotlin's Standard.kt

There are many functions, which change the meaning of this. In the good-old days of Java, this was clear. It always refers to an instance of the class that you are looking at.

But with Kotlin, that no longer holds.

class Foo {
    val bar = Bar()
    fun foo() {
        with (bar) {
            this.doSomething()
        }
    }
    
    // Assume there are lots more functions not shown.
}

with is a funky thing, not available in Java. It takes two parameters. The second one is the block of code containing this.doSomething(). Inside this block this has a new meaning. It no longer refers to an instance of Foo, but an instance of Bar.

We don't usually type, this though. The following is more common...

class Foo {
    val bar = Bar()
    fun foo() {
        with (bar) {
            doSomething()
            doSomethingElse()
        }
    }
    
    // Assume there are lots more functions not shown.
}

Does doSomethingElse() refer to a Bar or a Foo? There is no way to know without hitting F4 in the IDEA (a shortcut which jumps to method's code). That's pretty bad, because code readability is really important.

But today, I realised that there is a MUCH bigger problem with this code.

Let's say that doSomethingElse is a method of Foo. Kotlin is clever enough (or stupid enough) to look through all possible receivers. First it checks if Bar has a doSomethingElse() method, and when it doesn't find one, it then looks at Foo, and finds it. All is well.

So this code is actually the same as :

class Foo {
    val bar = Bar()
    fun foo() {
        with (bar) {
            this.doSomething()
            this@Foo.doSomethingElse()
        }
    }
    
    // Assume there are lots more functions not shown.
}

Now imagine two years later, a coworker adds a function to Bar, and by coincidence, She calls it doSomethingElse(). She's a genius, her code is perfect, and she has huge test coverage to prove it.

But we are now screwed! Despite not touching our code, it no longer works as expected. Kotlin finds the (new) method on Bar, and doesn't search any further.

Imagine how hard it would be to spot the bug by just looking at the code. Our code worked faultlessly for years, and it has NOT changed. Surely the fault must be elsewhere (it isn't). We are the dullard, not our genius coworker.

I've used this anti-pattern quite a bit in the past.

e.g. I find it really handy to build dynamic menus on the fly, like so...

fun buildMenu() = Menu().apply {
   if ( includeHello() ) {
       items.add( MenuItem( "Hello" ).apply {
           onAction = { doSomething() }
       })
   }
   // More menu items...
}

If JavaFX introduces a new method called ''includeHello()'' on class ''Menu'', I'm in trouble.

If JavaFX introduces a new method called ''doSomthing()'' on classes ''Menu'' or 'MenuItem'', I'm in trouble.

While writing this, it suddenly occurred to me, that I might be wrong.
Maybe the compiler looks at all possible receivers, and throws a compiler error if there's an ambiguity.
Nope! But it should!

One last example to ram the idea home...

The sales staff get an end of year bonus dependent on the number of happy customers they support.
Each call to ''addBonus()'' is worth a grand to them...
{{{
class SalesStaff {
   fun endOfYearBonus() {
       for (customer in happyCustomers() ) {
           with( customer ) {
               sendThankYouEmailToCustomer()
               addBonus()
           }
       }
   }
}

5 years down the line, the marketing department starts giving customers extra goodies (completely unrelated to staff bonuses), but by an unhappy coincidence, they called this new method addBonus().

Nothing crashes, no errors, the customers get inundated by extra goodies (that they shouldn't have been given), all of the sales people are really pissed off. They think they've done a great job, so they either leave, or don't bother to go the extra mile next year! The company folds.

The rival company with better coding style wins ;-)

Summary

The funky Kotlin functions which change the meaning of this, can make your code hard to read. But worse still, they can be very dangerous. You have been warned.

I used to regard Kotlin as an excellent OO language, far better than Java. Now, I'm not so sure.

This isn't the only gotcha that plagues Kotlin. Extension functions are wonderful, but have similar downsides.