SkillByAIOpen interactive version →

Lesson 12 / 25

Extension Functions and Scope Functions

Add functions to types; let, apply, run, also, with.

Extending without inheritance

An extension function fun String.isEmail(): Boolean adds a function callable as "a@b.c".isEmail() without modifying or subclassing String; it is resolved statically and cannot access private members. Extension properties work similarly. The scope functions run a block with an object in context: let (object as it, returns the lambda result; common with ?.let for nullable values), also (as it, returns the object; side effects such as logging), apply (as this, returns the object; configuration), run (as this, returns the lambda result) and with(obj) { } (non-extension form of run).

Extensions and scope functions together

Pick the function by what it returns.

fun String.isEmail(): Boolean = contains("@") && substringAfter("@").contains(".")

val List<Int>.median: Double?
    get() = if (isEmpty()) null else sorted().let { s ->
        if (s.size % 2 == 1) s[s.size / 2].toDouble()
        else (s[s.size / 2 - 1] + s[s.size / 2]) / 2.0
    }

class Request { var url = ""; var timeoutMs = 0; val headers = mutableMapOf<String, String>() }

fun build(token: String?): Request = Request().apply {   // configure, returns Request
    url = "https://api.example.com"
    timeoutMs = 5_000
    token?.let { headers["Authorization"] = "Bearer $it" }  // only if not null
}.also { println("built request for ${it.url}") }           // side effect, returns Request

Do not nest scope functions deeply

Chains of let inside apply inside run quickly become unreadable because it and this change meaning. If a block grows, extract a named function.

Quick check: Which scope function returns the object itself and exposes it as `this`?

  • `let`
  • `apply`
  • `run`
  • `with`
Answer

`apply` — apply is for configuring an object; also returns it too but uses it.