Kotlin Receiver Lambdas
A lambda with a receiver runs its block on an object. Inside the block, the members of that object are in scope with no qualifier.
A builder that runs a block on a fresh Box
boxOf creates a Box and runs the caller block on it. Inside the block, item resolves against the Box with no qualifier.
class Box {
val items = mutableListOf<String>()
fun item(name: String) {
items.add(name)
}
}
fun boxOf(init: Box.() -> Unit): Box {
val box = Box()
box.init()
return box
}
fun main() {
val box = boxOf {
item("pen")
item("book")
}
println(box.items.joinToString(", "))
}A receiver lambda stored in a variable
The variable build holds a lambda whose type names Label as the receiver. Passing it to labelOf runs it on a fresh Label.
class Label {
var text = ""
fun word(value: String) {
text += value
}
}
fun labelOf(init: Label.() -> Unit): Label {
val label = Label()
label.init()
return label
}
fun main() {
val build: Label.() -> Unit = {
word("sale")
word("!")
}
val label = labelOf(build)
println(label.text)
}this inside the block is the receiver
The block adds to the Counter through its member function. Printing this.total inside the block reads the same Counter that counterOf returns.
class Counter {
var total = 0
fun add(amount: Int) {
total += amount
}
}
fun counterOf(init: Counter.() -> Unit): Counter {
val counter = Counter()
counter.init()
return counter
}
fun main() {
val counter = counterOf {
add(3)
add(4)
println(this.total)
}
println(counter.total)
}Example
class Basket {
val entries = mutableListOf<String>()
fun add(name: String, count: Int) {
entries.add("$name x$count")
}
}
fun basketOf(init: Basket.() -> Unit): Basket {
val basket = Basket()
basket.init()
return basket
}
fun main() {
val basket = basketOf {
add("apple", 2)
add("pear", 1)
}
basket.entries.forEach { println(it) }
}Key points
- A function type can name a receiver type before the parentheses, as in Box.() -> Unit.
- A lambda with that type runs with an instance of the receiver type as its this.
- Inside the block, the members of the receiver are called directly, with no object name in front.
- A builder function creates a fresh receiver, runs the block on it, and returns it.
- A receiver lambda can also be stored in a variable with its receiver type written out.
In this example, basketOf builds a fresh Basket and runs the block on it. Each add call lands on the Basket because it is the receiver of the block. The stored entries print in the order the block added them.
Note
A plain lambda names its single parameter it, while a receiver lambda works through this.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Kotlin Playground
Exercise
What does the function type Box.() -> Unit describe?