As we already covered, Kotlin provides the ability to extend a class or an interface with new functionality without having to inherit from the class or use design patterns such as Decorator ⮕ Kotlin is fun
- extension functions.
Kotlin also treats functions as first-class citizens, meaning they can be passed around. It also, of course, supports generics ⮕ Kotlin is fun
- Function types, lambdas, and higher-order functions.
This means we can write something like this:
fun <T> T.doWith(op: (T) -> Unit) {
op(this)
}
This extension function can be called on anything and will simply pass this anything as an argument to the lambda passed as a parameter. For example:
val someList = listOf(1, 2, 3, 4)
someList.doWith ({ list ->
println(list.size)
println(list.sum())
})
This is syntax is not following the conventions. Let's rewrite it in proper Kotlin, by removing parenthesis and using the implicit it
parameter:
someList.doWith { // it -> List<Int>
println(it.size)
println(it.sum())
}
This is better. Notice though that the it
is redundant. We would rather be able to write:
someList.doWith { // this -> List<Int>
println(size)
println(sum())
}
But how? Read on!
Lambdas with receivers
To make this magic happen, the only necessity is to change the type of the op
argument from (T) -> Unit
to T.() -> Unit
. This special syntax, A.(B)
, denotes a receiver object:
Inside the body of the function literal, the receiver object passed to a call becomes an implicit this, so that you can access the members of that receiver object without any additional qualifiers, or access the receiver object using a
this
expression.
The final implementation is now:
fun <T> T.doWith(op: T.() -> Unit) {
op(this)
}
listOf(1, 2, 3, 4).doWith {
println(size)
println(this.sum()) // "this" is optional
}
It is just a bit of sugar-coating, but admit this syntax looks cool 😎.
Side note
In the real world, we would also want this doWith
to be inline, to improve performances:
inline fun <T> T.doWith(op: T.() -> Unit) {
op(this)
}
For more information on receivers (and more examples), see the docs on Function literals with receivers.