liveBook · Manning

Lambda with receiver

As explained in Kotlin in Action, 2E

A lambda with receiver is a lambda whose function type designates one parameter as an implicit receiver, allowing the lambda body to access that receiver's members without qualification.

A lambda with receiver gives an object a special role inside a lambda body: its members can be called without qualifying the object. This makes repeated work on one object more compact and gives the block a context distinct from surrounding code. Kotlin uses the pattern in standard functions such as with, apply, and buildString; Java has no equivalent receiver-lambda feature. section 5.4 section 5.5

Build a string without repeating its receiver

```kotlin fun alphabet() = buildString { for (letter in 'A'..'Z') append(letter) append("\nNow I know the alphabet!") } ``` The `buildString` block has a `StringBuilder` receiver. `append` is therefore shorthand for `this.append`; writing `this` remains useful when disambiguation is needed. A comparable ordinary lambda would require an explicit parameter such as `it.append(...)` on every call. section 13.2.1 section 5.4.1

Choose the standard scope function by its return value and intended operation:

  • `with(receiver) { ... }` makes `receiver` implicit and returns the lambda's last expression, fitting several operations whose computed result matters. section 5.4.1
  • `object.apply { ... }` makes the object implicit and returns that initialized object, fitting builder-style configuration. section 5.4.2
  • `object.also { ... }` returns the object but passes it as `it`; it is for additional effects, not a lambda with an implicit receiver. section 5.4.3

The type syntax is an extension function type. `StringBuilder.() -> Unit` means a function that runs with a `StringBuilder` receiver, whereas `(StringBuilder) -> Unit` supplies a normal explicit parameter. A caller invokes such a lambda on a receiver instance, as in `sb.builderAction()`. Receiver lambdas can also be stored in variables and invoked on compatible objects. section 13.2.1

Listing 13.2 Redefining buildString to take a lambda with a receiver
fun buildString(
    builderAction: StringBuilder.() -> Unit   #1
): String {
    val sb = StringBuilder()
    sb.builderAction()                        #2
    return sb.toString()
}
 
fun main() {
    val s = buildString {
        this.append("Hello, ")                #3
        append("World!")                      #4
    }
    println(s)
    // Hello, World!
}

Compare an ordinary `StringBuilder` lambda using `it.append` with a receiver lambda using `append` directly, including the corresponding function declaration and invocation. section 13.2.1

Nested receiver lambdas underpin structured Kotlin DSLs. In an HTML builder, each block contributes names resolved from its receiver type: `table` can expose `tr`, and `tr` can expose `td`. That structure can enforce a grammar through the API, but several receivers in scope can make an unqualified call confusing; labeled `this` can make the target explicit, and `@DslMarker` can constrain accidental access to outer receivers. section 13.2.2

Define a receiver-lambda builder

```kotlin fun buildString(builderAction: StringBuilder.() -> Unit): String { val sb = StringBuilder() sb.builderAction() return sb.toString() } ``` Here `sb` becomes the implicit `this` while `builderAction` runs. The same mechanism lets an `apply`-style helper invoke a block and then return its receiver. section 13.2.1

Use receiver lambdas when a block primarily configures, builds, or navigates one context object. They support concise object initialization, mutable construction of read-only collections, and expressive DSL blocks for markup, build dependencies, tests, and database APIs. The compactness is most valuable when the receiver is obvious; qualify or label calls when nested contexts would obscure which object owns a member. section 5.4.2 section 13.3.2

How Kotlin in Action, 2E covers it

How deep it goes
Explains syntax, invocation, return behavior, nested resolution, and a simplified HTML-builder implementation.
Examples
Uses alphabet construction, TextView initialization, collection builders, HTML tables, and Gradle dependency declarations.
What it assumes
Builds from ordinary lambdas and function types, then contrasts explicit parameters with implicit receivers.
What you can do after
Build it from scratch by declaring extension function types and invoking blocks on receiver instances.
How it connects
Connects extension function types, scope functions, DSL markers, the invoke convention, and internal DSLs.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.