Implement apply and with yourself using receiver lambdas. What are their exact signatures, and how do they differ from let?
answer
- apply: T.()->Unit, returns this
- with(receiver, T.()->R): R, not an extension
- let: (T)->R uses it, returns result
- Axis 1: this vs it; Axis 2: self vs result
- All inline
basics
~20 sapply runs a block on an object and returns the object; with does the same but returns the block's result. Both make the object 'this'. let differs: it passes the object as 'it' and returns the result.
solid answer
~40 sapply: `inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }` — an extension whose block is a receiver lambda; returns the receiver. with: `inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()` — a top-level function (not an extension) returning the block's result. Both expose the object as `this`, so members are called unqualified. let differs: `inline fun <T, R> T.let(block: (T) -> R): R = block(this)` uses a **parameter lambda**, so the object is `it`, not `this`, and it returns the block's result. Choose apply for configure-and-return-self, with/run for compute-a-result on a receiver, let for null-safe transforms (`a?.let { ... }`).
code
kotlin · 12 linesdata class Server(var host: String = "", var port: Int = 0)
val s = Server().apply { // this == Server, returns Server
host = "localhost"
port = 8080
}
val url = with(s) { // this == Server, returns String
"$host:$port"
}
val upper = s.host.let { it.uppercase() } // it == host, returns Stringgo deeper
Knows apply configures and returns the object; let transforms via it.
States exact signatures and the this-vs-it / self-vs-result axes, picks correctly.
Explains inline implications (non-local return, no allocation) and idiomatic usage.
Advises on team conventions to avoid scope-function overuse and nested-this confusion.
## Exact signatures ```kotlin // Receiver-lambda scope functions: object is `this` public inline fun <T> T.apply(block: T.() -> Unit): T { block() // == this.block() return this } public inline fun <T, R> with(receiver: T, block: T.() -> R): R { return receiver.block() } public inline fun <T> T.run(block: T.() -> R): R = block() // receiver form // Parameter-lambda scope functions: object is `it` public inline fun <T, R> T.let(block: (T) -> R): R = block(this) public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this } ``` ## The two axes Scope functions vary on **(1) how the object is referenced** and **(2) what they return**: - Reference as `this` (receiver lambda): `apply`, `with`, `run`. - Reference as `it` (parameter lambda): `let`, `also`. - Return the **object**: `apply`, `also`. - Return the **block result**: `with`, `run`, `let`. ## When to use which - **apply** — configure an object and get it back: `Button().apply { text = "OK"; isEnabled = true }`. - **with** — run several operations on a non-null object and return a result: `with(config) { host + ":" + port }`. Not an extension, so you pass the object explicitly. - **let** — transform a value, especially null-safe: `name?.let { it.trim().uppercase() }`. Object is `it`, so it reads well when you rename it (`name?.let { n -> ... }`). - **also** — side effects without changing the value: `value.also { log(it) }`. ## Why apply uses receiver and let uses parameter Apply is built for **configuration**, where calling members unqualified (`text = ...`) is the goal — a receiver lambda. Let is built for **transforming a value**, where naming it (`it` / a custom name) and possibly using a different result type reads better — a parameter lambda. All five are `inline`, so the lambda is inlined with no allocation and non-local `return` is allowed. ## Keywords/APIs - `inline` — enables zero-cost lambdas and non-local returns. - Receiver lambda `T.() -> R` vs parameter lambda `(T) -> R`. - `this` vs `it` resolution.
- Why is with not an extension function while apply is?with takes the receiver as an explicit first parameter (with(x){...}); apply is invoked on the object (x.apply{...}). The choice is ergonomic, not semantic.
- What does inline give these functions besides performance?It allows non-local returns from the lambda and avoids allocating a function object per call.
saying these in an interview costs you the question
- Says apply returns the block result
- Claims let exposes the object as this
- Writes with as an extension (x.with { })
- Forgets these are inline and thinks each call allocates