🏎️ Β· Qualified This βš™οΈ

4 min read Β· Updated on by

Learn about an interesting problem that arises when extensions are introduced, and how to solve it using labels and qualified this.

With the introduction of extension functions and functions with receiver, we open the doors to an interesting situation. Take a look at the following:


//sampleStart
class A(val x: Int)

class B(val x: Int) {
    fun A.printX() = println(x)
    
    fun runPrintX(xForA: Int) = A(xForA).printX()
}

// Will this print 3 or 5?
fun main() = B(3).runPrintX(5)
//sampleEnd

The problem with the above is that, in A.printX, there are actually two candidate values for this, and therefore two candidates for x β€” an A instance is in scope because printX is an extension function on A, and a B instance is in scope because A.printX is also a method (i.e. defined inside B). While the example may seem synthetic, it is not all that uncommon for this to happen.

A slightly different but equivalent situation can arise with function literals with receiver:


//sampleStart
class A(val x: Int)
class B(val x: Int)

fun A.runOnA(block: A.() -> Unit) = block()
fun B.printX(xForA: Int) = A(xForA).runOnA { println(x) }

// Will this print 3 or 5?
fun main() = B(3).printX(5)
//sampleEnd

The issue here is almost identical β€” inside the runOnA block, we have two candidate values for this β€” the block is a function with receiver of type A, so an A instance is in scope inside the literal, but block is also defined as part of an extension function on B, which means that we have a B instance in scope as well.

Qualified this

This is identical to the situation encountered withΒ inner classes, and the solution is also identical:Β if not qualified, aΒ thisΒ expression refers to the innermost enclosing scope. If we want to refer to a different one, we need to qualify theΒ thisΒ expression with a label by writingΒ this@label.

Implicit labels are created for every class body and function statement (not literal) body. Here is an example describing all the possibilities:


//sampleStart
class A { // implicit label @A
    inner class B { // implicit label @B
        fun Int.foo() { // implicit label @foo
            val a = this@A // A's this
            val b = this@B // B's this

            val c = this // innermost receiver, which means foo()'s receiver, an Int
            val c1 = this@foo // foo()'s receiver, an Int
        }
    }
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the philosopher in code's grand tale,
        With extension functions, it sets the sail.
        From chapters to verses, a narrative so true,
        In the world of programming, it's the cue!
    """.trimIndent()
    println(poem)
}

This solves our problem when dealing with the receivers of statements (class and function definitions), but it still doesn’t help us with literals (anonymous functions & lambdas with or without receivers). However, as was mentioned in the chapter onΒ inner classes, (explicit)Β labels can also be used to explicitly qualify expressions. You can use that to your advantage when dealing with conflicting receivers inside literals:


//sampleStart
val funLit = lambda@ fun String.() {
    class A {
        val b = this@lambda // funLit's receiver   
    }
}
//sampleEnd
fun main() {
    val poem = """
        When you're in the gallery of code's great art,
        Kotlin's syntax is the masterpiece's heart.
        With strokes and colors, a canvas so true,
        In the coding exhibition, it's the view!
    """.trimIndent()
    println(poem)
}

Exercises

It’s brain-fuck time! Spend some time thinking about what the abc function prints. Remember that -> is right associative, so the type bellow
is the same as A.() -> (B.() -> (C.() -> Unit)).

Start reading: "The function 'abc', when invoked, returns a function
with receiver A, which, when invoked, returns a function...". Contrast this with the number of invocations in main.

Think about what this function prints when run in its current form. Run the example β€” were you right?


//sampleStart
object A
object B
object C

/**
 * Think about what this function prints when run in its current form. Run the example - were you right?
 */
fun abc(): A.() -> B.() -> C.() -> Unit = A@ {
    B@ {
        C@ {
            println("${this::class.java.simpleName} ${this::class.java.simpleName} ${this::class.java.simpleName}")
        }
    }
}

fun main() {
    // You'll learn about with() in the chapter on scope functions. Don't worry about it now.
    with(A) {
        with(B) {
            with(C) {
                abc()()()()
            }
        }
    }
}
//sampleEnd

Make changes so it evaluates to A B C:


import org.junit.Assert
import org.junit.Test

class Test {
    @Test fun testSolution() {
        with(A) {
                with(B) {
                    with(C) {
                        Assert.assertEquals("Incorrect implementation of abc.", "A B C", abc()()()())
                    }
                }
            }
    }
}

object A
object B
object C

//sampleStart
/**
 * Make changes so abc() evaluates to "A B C"
 */
fun abc(): A.() -> B.() -> C.() -> String = A@ {
    B@ {
        C@ {
            "${this::class.java.simpleName} ${this::class.java.simpleName} ${this::class.java.simpleName}"
        }
    }
}
//sampleEnd

Solution

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer