Kotlin is `fun` - lambdas with receivers

Lucy Linder - Apr 11 '23 - - Dev Community

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)
}
Enter fullscreen mode Exit fullscreen mode

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())
})
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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())
}
Enter fullscreen mode Exit fullscreen mode

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 
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

For more information on receivers (and more examples), see the docs on Function literals with receivers.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .