DSL и функции с получателем в Kotlin
DSL и функции с получателем
Напоминание: общие понятия функций в программе — функции в коде.
Когда вы пишете в Ktor:
routing {
get("/health") { call.respond(mapOf("status" to "ok")) }
}
блок routing { } выглядит как мини-язык конфигурации. На самом деле это обычный Kotlin: лямбда с получателем и extension-функции. Такой стиль называют DSL (предметно-ориентированный язык внутри языка).
Примеры в продакшене: Ktor, Gradle build.gradle.kts, HTML-генераторы, тестовые билдеры.
Теория синтаксиса: синтаксические конструкции. Стандартные apply / run: встроенные функции.
Словарь терминов
| Термин | Простыми словами |
|---|---|
| DSL | API, который читается как конфиг, а не как цепочка вызовов с точками. |
| Лямбда | Анонимная функция: { x -> x * 2 }. |
| Receiver (получатель) | Внутри блока доступен this как у метода класса. |
| Extension-функция | Функция «как будто метод» существующего типа: fun String.lastChar(). |
@DslMarker | Защита от вызова «чужих» функций во вложенных DSL-блоках. |
Обычная лямбда и лямбда с получателем
Обычная — аргумент передаётся явно:
val greet: (String) -> Unit = { name -> println("Hi, $name") }
greet("Ann")
С получателем — внутри блока this имеет тип T:
fun buildString(block: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.block() // вызов extension-лямбды на sb
return sb.toString()
}
val text = buildString {
append("Hello")
append(", ")
append("Kotlin")
}
Разбор:
StringBuilder.() -> Unit— тип «функция без аргументов, но с receiverStringBuilder».- Внутри
{ append("Hello") }компилятор подставляетthis.append— как если бы вы писали методы уStringBuilder. - Стандартная библиотека даёт те же приёмы:
buildList,buildMap,apply,run,with.
val config = mutableMapOf<String, Any>().apply {
put("host", "localhost")
put("port", 8080)
}
apply возвращает сам объект после настройки.
Свой мини-DSL — меню
class MenuBuilder {
private val items = mutableListOf<String>()
fun item(text: String) {
items += text
}
fun build(): List<String> = items.toList()
}
fun menu(block: MenuBuilder.() -> Unit): List<String> =
MenuBuilder().apply(block).build()
val lunch = menu {
item("Суп")
item("Салат")
item("Чай")
}
| Шаг | Что видит новичок |
|---|---|
menu { } | Вызов функции с лямбдой |
item("Суп") | Как будто ключевое слово языка |
| Реальность | Метод MenuBuilder.item на неявном this |
Ktor делает то же с Route: внутри routing { } receiver — объект маршрутизатора, get — его метод.
@DslMarker — не перепутать вложенные блоки
Без маркера во вложенном DSL можно случайно вызвать функцию «соседнего» уровня. Аннотация ограничивает видимость:
@DslMarker
annotation class HtmlTagMarker
@HtmlTagMarker
abstract class Tag {
protected val children = mutableListOf<Tag>()
}
@HtmlTagMarker
class Body : Tag() {
fun p(text: String) { /* добавить параграф */ }
}
@HtmlTagMarker
class Html : Tag() {
fun body(block: Body.() -> Unit) {
val b = Body()
b.block()
children += b
}
}
fun html(block: Html.() -> Unit): Html = Html().apply(block)
Вложенный body { p("…") } корректен; вызов p на уровне html { } компилятор отклонит.
Infix и операторы
infix fun Int.days(unit: String) = "$this $unit"
val period = 7 days "дней"
infix позволяет писать вызов без точки и скобок — для читаемости DSL.
Операторы для своих типов:
data class Vec2(val x: Int, val y: Int)
operator fun Vec2.plus(other: Vec2) = Vec2(x + other.x, y + other.y)
val sum = Vec2(1, 2) + Vec2(3, 4)
Используйте + только там, где сложение векторов ожидаемо — иначе DSL перестаёт быть понятным.
Упрощённый аналог Ktor routing
class RouteConfig {
val routes = mutableListOf<Pair<String, () -> Unit>>()
fun get(path: String, handler: () -> Unit) {
routes += path to handler
}
}
fun routing(block: RouteConfig.() -> Unit): RouteConfig =
RouteConfig().apply(block)
val app = routing {
get("/health") { println("ok") }
get("/version") { println("1.0") }
}
Реальный Ktor добавляет call, HTTP-методы, плагины — но скелет тот же.
Gradle Kotlin DSL
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}
dependencies { } — функция с receiver типа DependencyHandlerScope. Вы «настраиваете» объект Gradle, а не вызываете десятки статических методов подряд.
Когда свой DSL избыточен
| Ситуация | Лучше |
|---|---|
| Три поля конфигурации | data class + именованные аргументы |
| Одноразовый тест | обычные функции |
| Команда слабо знает Kotlin | JSON/YAML конфиг |
DSL окупается при иерархической настройке (маршруты, дерево UI, вложенные теги) и повторном использовании.
Связанные материалы
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). История Kotlin - создание языка JetBrains для плавной совместимости с Java и современной разработки. Экосистема Kotlin-приложений - инструменты, фреймворки и мультиплатформенные сценарии разработки. Kotlin — это современный язык программирования общего назначения, который работает на платформе Java Virtual Machine (JVM), а также компилируется в JavaScript или нативный код. Набор советов, правил, принципов и обычаев в разработке на этом языке. Основы Kotlin - архитектура проекта, платформенные модули и базовые принципы разработки на языке. Типизация, набор правил определения типа данных значений языка. Операторы и выражения в Kotlin - логика вычислений, приоритеты операций и идиоматичный стиль записи условий. Циклы и управляющие конструкции в Kotlin - идиоматичные способы итерации, условия и управление выполнением. ООП в Kotlin - классы, модификаторы доступа, наследование и идиоматичное проектирование объектных структур. Кавычки, точки, запятые, скобки и прочие знаки препинания. Kotlin использует набор зарезервированных слов для построения синтаксических конструкций языка. Все ключевые слова разделены на категории по назначению. Набор функций, которые включены в стандартную библиотеку языка.История языка Kotlin
Экосистема Kotlin-приложений
Что требуется знать перед началом изучения языка программирования Kotlin
Рекомендации по разработке на Kotlin
Основы языка Kotlin
Типы данных и объявление переменных
Операторы и выражения в Kotlin
Циклы и управляющие конструкции
Объектно-ориентированное программирование в Kotlin
Синтаксис и пунктуация в Kotlin
Ключевые слова языка Kotlin
Встроенные функции и расширения Kotlin