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