liveBook · Manning

Internal DSL

As explained in Kotlin in Action, 2E

An internal DSL is a domain-specific language embedded in a host programming language and expressed using the host language's syntax and features.

An internal DSL is a domain-specific API written inside a general-purpose language. Rather than placing SQL, HTML, or another notation in a separate file or string, it uses ordinary Kotlin syntax, methods, types, and IDE support. This keeps domain code close to the application while retaining a focused, language-like vocabulary. section 13.1.1 section 13.1.2

A database query expressed as Kotlin

An Exposed query is regular Kotlin code, yet its chained calls read as a query: join tables, choose columns, group results, order them, and take one row. Methods such as selectAll, groupBy, and orderBy form the DSL; execution yields native Kotlin objects while generating the same SQL as the direct query. section 13.1.2

(Country innerJoin Customer)
   .slice(Country.name, Count(Customer.id))
   .selectAll()
   .groupBy(Country.name)
   .orderBy(Count(Customer.id), order = SortOrder.DESC)
   .limit(1)

Compare this chained Kotlin query with its SQL equivalent to see how a library supplies domain vocabulary without introducing separate syntax. section 13.1.2

A DSL becomes more than a sequence of unrelated calls when its operations have grammar: calls combine into a meaningful structure, and Kotlin’s type checker constrains valid combinations. Chained calls commonly express a pipeline, while lambdas with receivers create nested scopes. Function names tend to act like verbs and their arguments like nouns. section 13.1.3 section 13.5

Use internal DSL structure to make domain operations clearer and less repetitive:

  • Nest a receiver lambda when operations belong to a shared context, as in a Gradle dependencies block. section 13.1.3
  • Chain calls when successive operations refine one result, as in a database query or a readable assertion. section 13.1.3
  • Extract repeated DSL fragments into Kotlin functions, because DSL operations are ordinary functions. section 13.2.3

Extending an HTML builder

A Kotlin HTML builder can first mirror raw HTML with nested ul, li, h2, and p calls. A higher-level listWithToc block then hides table-of-contents bookkeeping behind item calls. Its implementation collects headline-body pairs in a builder object; an extension on BODY creates that builder, invokes the receiver lambda, and renders the entries. section 13.2.3

Listing 13.15 Building a page with a table of contents with helper functions
fun buildBookList() = createHTML().body {
   listWithToc {
       item("The Three-Body Problem", "The first book tackles...")
       item("The Dark Forest", "The second book starts with...")
       item("Death’s End", "The third book contains...")
   }
}

This helper-based HTML builder shows the intended DSL surface: describe entries with item calls instead of manually maintaining markup and links. section 13.2.3

Internal DSLs trade a wholly separate syntax for expressive API design. They can preserve readable, task-specific code while enabling compile-time checking, code assistance, debugging in the host language, and reuse through ordinary Kotlin abstractions. Kotlin libraries use this approach for HTML generation, database access, dependency declarations, and test assertions. section 13.1.1 section 13.5

How Kotlin in Action, 2E covers it

How deep it goes
Explains DSL grammar through chaining and receiver-lambda nesting, then demonstrates reusable custom HTML-builder abstractions.
Examples
Uses Exposed database queries, Gradle dependencies, Kotest assertions, and kotlinx.html table-of-contents builders.
What it assumes
Builds from ordinary Kotlin functions and method calls before introducing receiver-lambda structure and extension function types.
What you can do after
Build it from scratch by defining builder state, receiver-lambda entry points, extension functions, and reusable DSL helper functions.
How it connects
Connects lambdas with receivers, extension function types, chained method calls, type checking, and API design.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.