DEV Community

Cover image for Kotlin is `fun` - lambdas with receivers
Lucy Linder
Lucy Linder

Posted on • Originally published at blog.derlin.ch

4

Kotlin is `fun` - lambdas with receivers

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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more