Kotlin
2026-08-21 09:00
Extension functions and infix calls — unit 3.
Lambdas — unit 5. Operator conventions — unit 7. Higher-order functions — unit 8.
It assembles.
Reflection achieved flexibility at runtime, giving up static checking.
DSLs achieve expressiveness at compile time, keeping every guarantee.
None is a string. None is parsed at runtime.
Each is ordinary Kotlin, type-checked, with IDE completion.
invoke convention.A DSL is a vocabulary readers must learn.
Sometimes an excellent bargain.
Sometimes a plain function would have been kinder.
A general-purpose language does everything adequately.
A domain-specific one does one thing well, giving up generality.
SQL. Regular expressions.
Describe what the result is, not how to get it.
Which is where the concision comes from —
and why a SQL engine can reorder your query.
The compiler cannot check a string.
The IDE cannot complete it. Refactoring cannot rename through it.
The same query, checked, completed, safely refactored.
Clean API design gives readable calls.
A DSL gives structure — a grammar.
Command-query API — each call stands alone. A flat sequence.
Structured DSL — calls nest and chain.
Which is why an HTML DSL looks like HTML.
Which is why the next section
is the central one in the unit.
Nested several levels deep, that is the difference between a DSL
and a pile of punctuation.
apply, with, run — all of them.
It stops looking like a special form.
It becomes an ordinary function you could have written.
Inside tr { }, this is a TR.
So only the functions declared on TR are available.
Nesting — from lambdas containing other calls.
Static checking — from the receiver type controlling what is legal inside.
Not something a string template can offer at any price.
Nor achievable by naming discipline. It falls out of the receiver types.
The vocabulary is available inside the block and invisible outside it.
Receiver-scoped vocabulary.
Statically checked nesting.
Unit 7’s convention mechanism, applied to the call syntax.
Function types are interfaces with an invoke method.
Which is why a lambda in a variable can be called with parentheses.
And why unit 8’s nullable function parameter needed ?.invoke().
Both, from one object.
invoke loosens the nesting requirement.
A block form for many items, a direct form for one —
without duplicating the API.
invoke on an arbitrary class makes calls unreadable.
person("Alice") tells the reader nothing.
It earns its place in a DSL, where structure supplies the meaning.
The mechanisms are known.
The work is recognising them in the wild.
An infix function combined with an extension.
Extension properties — doing something a subclass could not. You cannot subclass Int.
Member extensions — autoIncrement() exists only inside a Table.
Infix functions — eq, less, like.
Lambdas with receivers — the query block.
A renamed column is a compile error.
Not a broken query discovered in production.
Every one is ordinary Kotlin.
No macro system. No metaprogramming. No runtime parsing.
A small set of features composing into what looks like language design.
A vocabulary readers must learn.
Indirection between what is written and what happens.
The standing temptation to be clever.
In a domain that is repetitive and genuinely has structure.
Markup. Queries. Builds. Tests. UI layout.
Does the DSL make the reader’s job easier?
A DSL that is delightful to write and puzzling to read has failed,
however pleased its author was.
A handful of calls → write the functions.
A hundred structurally similar ones → build the DSL.
Extensions and infix — unit 3.
Lambdas — unit 5. Conventions — unit 7. Higher-order functions — unit 8.
None of them was introduced for DSLs.
Kotlin’s power is not in any individual feature — most are small —
but in how cleanly they compose.
A language of small, orthogonal, well-chosen features
lets its users build things its designers never specified.
That is a claim about language design, not just about Kotlin.
T.() -> Unit is the mechanism — and apply was using it all along.invoke is unit 7’s convention applied to the call itself.Small features that compose
beat big features that do not.
Kotlin · DSL Construction