skip to content

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.

level: seniorimportance: should knowfreq 30%

answer

  1. Receiver.() and suspend are part of the function type
  2. typealias HtmlBlock = Html.() -> Unit
  3. typealias AsyncOp<T> = suspend () -> T
  4. they compose: suspend Connection.() -> T
  5. pairs with @DslMarker for DSLs

basics

~20 s

Yes. 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 s

A 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
kotlin
@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

for a junior

Aware a lambda can have a receiver and that suspend exists for coroutines.

for a middle

Can write a receiver alias Html.() -> Unit and a suspend alias and use them in a builder.

for a senior

Explains that receiver/suspend are part of the type, compose with generics, and the alias is pure expansion.

for a principal

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

context