Can a function-type alias capture a receiver type or a suspend modifier? Show how you'd alias a DSL builder lambda and an async operation.
answer
- Receiver.() and suspend are part of the function type
- typealias HtmlBlock = Html.() -> Unit
- typealias AsyncOp<T> = suspend () -> T
- they compose: suspend Connection.() -> T
- pairs with @DslMarker for DSLs
basics
~20 sYes. You can alias a function type with a receiver, like typealias HtmlBlock = Html.() -> Unit, and a suspending one, like typealias AsyncOp = suspend () -> Result. The modifiers are part of the function type the alias names.
solid answer
~40 sA function-type alias can include both a **receiver** and the **suspend** modifier because they are part of the function type itself. Receiver form: `typealias HtmlBlock = Html.() -> Unit`; inside such a lambda `this` is the `Html` receiver, which powers type-safe builders/DSLs. Suspend form: `typealias AsyncOp<T> = suspend () -> T` or `typealias Fetcher = suspend (Url) -> Response`. You can combine them: `typealias Tx = suspend Connection.() -> Unit`. The alias is still a pure compile-time expansion, so `HtmlBlock` is exactly `Html.() -> Unit` and `AsyncOp<T>` is exactly `suspend () -> T`. This makes DSL and coroutine signatures far more readable without changing semantics; you can even attach `@DslMarker` scoping through the receiver type the alias references.
code
kotlin · 14 lines@DslMarker annotation class HtmlDsl
@HtmlDsl class Html { fun text(s: String) {} }
typealias HtmlBlock = Html.() -> Unit // receiver
typealias AsyncOp<T> = suspend () -> T // suspend
typealias Tx<T> = suspend Connection.() -> T // both + generic
fun html(block: HtmlBlock): Html = Html().apply(block)
suspend fun <T> runAsync(op: AsyncOp<T>): T = op()
class Connection
suspend fun <T> Connection.tx(block: Tx<T>): T = block()go deeper
Aware a lambda can have a receiver and that suspend exists for coroutines.
Can write a receiver alias Html.() -> Unit and a suspend alias and use them in a builder.
Explains that receiver/suspend are part of the type, compose with generics, and the alias is pure expansion.
Designs readable DSL/coroutine public APIs using aliases plus @DslMarker scoping and understands type-distinctness implications.
## Receiver function types A **function type with receiver** is written `Receiver.(Params) -> Return`. Inside a lambda of this type, `this` refers to the receiver, enabling type-safe builder DSLs. You can alias it: ```kotlin class Html { fun body(block: Html.() -> Unit) { /* ... */ } } typealias HtmlBlock = Html.() -> Unit fun html(block: HtmlBlock): Html = Html().apply(block) html { // `this` is Html body { /* ... */ } } ``` The receiver is part of the function type, so the alias preserves it. This is the backbone of DSLs (Ktor routing, kotlinx.html, Gradle Kotlin DSL). It often pairs with **`@DslMarker`** annotations on the receiver class to restrict implicit-receiver scope. ## Suspend function types The **`suspend`** modifier marks a function type usable inside coroutines (it can suspend execution). It's part of the function type and is aliasable: ```kotlin typealias AsyncOp<T> = suspend () -> T typealias Fetcher = suspend (Url) -> Response fun launchOp(op: AsyncOp<Int>) { /* call op() inside a coroutine */ } ``` A `suspend () -> T` is a *different* function type from `() -> T` (you can't call it outside a coroutine), and the alias faithfully carries that distinction. ## Combining receiver + suspend (+ generics) All modifiers compose, and you can parameterize the alias: ```kotlin typealias Tx<T> = suspend Connection.() -> T suspend fun <T> Connection.transaction(block: Tx<T>): T = block() ``` Here a `Tx<T>` lambda is suspending *and* has a `Connection` receiver, so inside it you can `await` and call connection methods on `this`. ## Why it matters - DSL parameter lists become self-documenting (`HtmlBlock` vs `Html.() -> Unit` repeated everywhere). - Coroutine APIs read clearly (`AsyncOp`, `Fetcher`). - Still zero runtime cost; the alias expands to the full modified function type. ## Gotcha The alias does **not** add or remove the receiver/suspend — it only names whatever the right-hand side declares. `typealias Plain = () -> Unit` cannot be used where `Html.() -> Unit` is required (different types), and a plain `() -> T` alias is not suspendable.
- Is `suspend () -> T` the same type as `() -> T`?No. The suspend modifier makes it a distinct function type callable only from a coroutine/suspend context; the alias preserves that distinction.
- Can the alias add `@DslMarker` scoping itself?Not directly; @DslMarker goes on the receiver class. The alias just references that receiver type, so the scoping carries through.
The alias is a label on a parcel: it names the parcel exactly as packed — including the 'fragile' (suspend) and 'this-side-up' (receiver) stickers — but adds none of them itself.
saying these in an interview costs you the question
- Claiming receiver or suspend cannot appear in an aliased function type
- Saying `suspend () -> T` equals `() -> T`
- Thinking the alias itself adds the receiver or suspend behavior
- Believing you can call a suspend-typed alias outside a coroutine
- Putting @DslMarker on the typealias instead of the receiver class