
This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.
build.gradle files that you might have seen in projects are just Groovy code:// build.gradle // Groovy plugins { id 'java' } dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' implementation "org.jb.ktx:kotlinx-coroutines-core:1.6.0" testImplementation "io.mockk:mockk:1.12.1" testImplementation "org.junit.j:junit-jupiter-api:5.8.2" testRuntimeOnly "org.junit.j:junit-jupiter-engine:5.8.2" }
Some dependencies are shortened to match book width.
// build.gradle.kts // Kotlin plugins { java } dependencies { implementation(kotlin("stdlib")) implementation("org.jb.ktx:kotlinx-coroutines-core:1.6.0") testImplementation("io.mockk:mockk:1.12.1") testImplementation("org.junit.j:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.j:junit-jupiter-engine:5.8.2") }

Kotlin DSL is fully statically typed; so, at every point we are given suggestions of what we can do, and if you make a typo, it is immediately marked.
// Kotlin body { div { a("https://kotlinlang.org") { target = ATarget.blank +"Main site" } } +"Some content" }

HTML view generated from the above HTML DSL.


// Kotlin class HelloWorld : View() { override val root = hbox { label("Hello world") { addClass(heading) } textfield { promptText = "Enter your name" } } }

View from the above TornadoFX DSL
fun Routing.api() { route("news") { get { val newsData = NewsUseCase.getAcceptedNews() call.respond(newsData) } get("propositions") { requireSecret() val newsData = NewsUseCase.getPropositions() call.respond(newsData) } } // ... }
fun Routing.setupRedirect(redirect: Map<String, String>) { for ((path, redirectTo) in redirect) { get(path) { call.respondRedirect(redirectTo) } } }

Spring security can be configured with the Kotlin DSL.
class MyTests : StringSpec({ "length should return size of string" { "hello".length shouldBe 5 } "startsWith should test for a prefix" { "world" should startWith("wor") } })
// Named extension function fun String.myPlus1(other: String) = this + other fun main() { println("A".myPlus1("B")) // AB // Anonymous extension function assigned to a variable val myPlus2 = fun String.(other: String) = this + other println(myPlus2.invoke("A", "B")) // AB println(myPlus2("A", "B")) // AB println("A".myPlus2("B")) // AB }
User.() -> Unit- a type representing an extension function onUserthat expects no arguments and returns nothing significant.Int.(Int) -> Int- a type representing an extension function onIntthat expects a single argument of typeIntand returnsInt.String.(String, String) -> String- a function type representing an extension function onStringthat expects two arguments of typeStringand returnsString.
myPlus2 is an extension function on String; it expects a single argument of type String and returns String, so its function type is String.(String) -> String.fun main() { val myPlus2: String.(String) -> String = fun String.(other: String) = this + other println(myPlus2.invoke("A", "B")) // AB println(myPlus2("A", "B")) // AB println("A".myPlus2("B")) // AB }
this keyword).fun main() { val myPlus3: String.(String) -> String = { other -> this + other // Inside, we can use receiver `this`, // that is of type `String` } // Here, there is no receiver, so `this` has no meaning println(myPlus3.invoke("A", "B")) // AB println(myPlus3("A", "B")) // AB println("A".myPlus3("B")) // AB }
this can help us introduce more convenient syntax to define some object properties. Imagine that you need to deal with classic JavaBeans objects: the initialized classes are empty, so we need to set all their properties using setters. These used to be quite popular in Java, and we can still find them in a variety of libraries. As an example, let's take a look at the following dialog[^10_3] definition:class Dialog { var title: String = "" var message: String = "" var okButtonText: String = "" var okButtonHandler: () -> Unit = {} var cancelButtonText: String = "" var cancelButtonHandler: () -> Unit = {} fun show() { /*...*/ } } fun main() { val dialog = Dialog() dialog.title = "Some dialog" dialog.message = "Just accept it, ok?" dialog.okButtonText = "OK" dialog.okButtonHandler = { /*OK*/ } dialog.cancelButtonText = "Cancel" dialog.cancelButtonHandler = { /*Cancel*/ } dialog.show() }
dialog variable with every property we want to set is not very convenient. So, let's use a trick: if we use a lambda expression with a receiver of type Dialog, we can reference these properties implicitly because this can be used implicitly.fun main() { val dialog = Dialog() val init: Dialog.() -> Unit = { title = "Some dialog" message = "Just accept it, ok?" okButtonText = "OK" okButtonHandler = { /*OK*/ } cancelButtonText = "Cancel" cancelButtonHandler = { /*Cancel*/ } } init.invoke(dialog) dialog.show() }
showDialog.fun showDialog(init: Dialog.() -> Unit) { val dialog = Dialog() init.invoke(dialog) dialog.show() } fun main() { showDialog { title = "Some dialog" message = "Just accept it, ok?" okButtonText = "OK" okButtonHandler = { /*OK*/ } cancelButtonText = "Cancel" cancelButtonHandler = { /*Cancel*/ } } }
showDialog ourselves, we could use the generic apply function, which is an extension function on any type. It helps us create and call a function type with a receiver on any object we want.// Simplified apply implementation inline fun <T> T.apply(block: T.() -> Unit): T { this.block() // same as block.invoke(this) return this }
Dialog, apply all modifications, and then explicitly show it.fun main() { Dialog().apply { title = "Some dialog" message = "Just accept it, ok?" okButtonText = "OK" okButtonHandler = { /*OK*/ } cancelButtonText = "Cancel" cancelButtonHandler = { /*Cancel*/ } }.show() }
showDialog function. However, apply helps only in simple cases, and it is not enough for more complex multi-level object definitions.apply useful for DSL definitions. We can simplify showDialog by using it to call init.fun showDialog(init: Dialog.() -> Unit) { Dialog().apply(init).show() }
buildList is a function from Kotlin stdlib used to create a list by adding elements implicitly to receiver.fun main() { val list = buildList { add(1) add(2) add(3) } println(list) // [1, 2, 3] }
add implicitly on the receiver, which is a MutableList. This is how this function could be implemented:fun <T> buildList(init: MutableList<T>.() -> Unit): List<T>{ val list = mutableListOf<T>() list.init() return list // or just // return mutableListOf<T>().apply(init) }
secondsToPrettyTime, that displays time in appropriate format. One of its simplest solutions uses buildList function:fun secondsToPrettyTime(allSeconds: Int): String { if (allSeconds < 0) return "Invalid input" val hours = allSeconds / SEC_IN_HOUR val minutes = (allSeconds % SEC_IN_HOUR) / SEC_IN_MINUE val seconds = allSeconds % SEC_IN_MINUE return buildList { if (hours > 0) add("$hours h") if (minutes > 0) add("$minutes min") if (seconds > 0) add("$seconds sec") }.joinToString(separator = " ") } private const val SEC_IN_HOUR = 60 * 60 private const val SEC_IN_MINUE = 60
buildString, where receiver is of type StringBuilder. It is used to create a string by appending elements implicitly to receiver.fun main() { val string = buildString { append("A") append("B") append("C") } println(string) // ABC }
buildMap and buildSet functions, and external libraries offer similar functions for constructing other types of objects, so it is worth remembering this pattern.Dialog has been refactored, and there is now a class that stores button properties:class Dialog { var title: String = "" var message: String = "" var okButton: Button? = null var cancelButton: Button? = null fun show() { /*...*/ } class Button { var message: String = "" var handler: () -> Unit = {} } }
showDialog is not enough because we need to create the buttons in the classic way:fun main() { showDialog { title = "Some dialog" message = "Just accept it, ok?" okButton = Dialog.Button() okButton?.message = "OK" okButton?.handler = { /*OK*/ } cancelButton = Dialog.Button() cancelButton?.message = "Cancel" cancelButton?.handler = { /*Cancel*/ } } }
fun makeButton(init: Dialog.Button.() -> Unit) { return Dialog.Button().apply(init) } fun main() { showDialog { title = "Some dialog" message = "Just accept it, ok?" okButton = makeButton { message = "OK" handler = { /*OK*/ } } cancelButton = makeButton { message = "Cancel" handler = { /*Cancel*/ } } } }
makeButton function that is used to create a button. In general, we prefer to require users to remember as little as possible. Instead, we could make okButton and cancelButton methods inside Dialog to create buttons. Such functions are easily discoverable and their usage is really readable.class Dialog { var title: String = "" var message: String = "" private var okButton: Button? = null private var cancelButton: Button? = null fun okButton(init: Button.() -> Unit) { okButton = Button().apply(init) } fun cancelButton(init: Button.() -> Unit) { cancelButton = Button().apply(init) } fun show() { /*...*/ } class Button { var message: String = "" var handler: () -> Unit = {} } } fun showDialog(init: Dialog.() -> Unit) { Dialog().apply(init).show() } fun main() { showDialog { title = "Some dialog" message = "Just accept it, ok?" okButton { message = "OK" handler = { /*OK*/ } } cancelButton { message = "Cancel" handler = { /*Cancel*/ } } } }
title inside of okButton.fun main() { showDialog { title = "Some dialog" message = "Just accept it, ok?" okButton { title = "OK" // This sets the dialog title! handler = { /*OK*/ } } cancelButton { message = "Cancel" handler = { /*Cancel*/ } } } }
okButton, elements will be suggested that should not be used. This also makes it easy to make a mistake.
DslMarker meta-annotation. A Meta-annotation is an annotation to an annotation class; so, to use DslMarker, we need to define our own annotation. In this case, we might call it DialogDsl. When we add this annotation before classes used in our DSL, it solves our safety problem[^10_4]. When we use it to annotate builder methods, it colors those functions’ calls.@DslMarker annotation class DialogDsl @DialogDsl class Dialog { var title: String = "" var message: String = "" private var okButton: Button? = null private var cancelButton: Button? = null @DialogDsl fun okButton(init: Button.() -> Unit) { okButton = Button().apply(init) } @DialogDsl fun cancelButton(init: Button.() -> Unit) { cancelButton = Button().apply(init) } fun show() { /*...*/ } @DialogDsl class Button { var message: String = "" var handler: () -> Unit = {} } } @DialogDsl fun showDialog(init: Dialog.() -> Unit) { Dialog().apply(init).show() }

DialogDsl to something else, you will most likely change the color of this DSL function call.
The four possible styles for DSL elements can be customized in IntelliJ IDEA.
DslMarker, we have a complete DSL example. Nearly all DSLs can be defined in the same way. To make sure we understand this completely, we will analyze a slightly more complicated example.val html = html { head { title = "My websi" + "te" style("Some CSS1") style("Some CSS2") } body { h1("Title") h3("Subtitle 1") +"Some text 1" h3("Subtitle 2") +"Some text 2" } }
html { ... } is. What is that? This is a function call with a lambda expression that is used as an argument.fun html(init: HtmlBuilder.() -> Unit): HtmlBuilder = TODO()
head and body only make sense inside html lambda expression, so they need to be called on its receiver. We will define them inside HtmlBuilder. Since they have children, they will have receivers: HeadBuilder and (my favorite) BodyBuilder.class HtmlBuilder { fun head(init: HeadBuilder.() -> Unit) { /*...*/ } fun body(init: BodyBuilder.() -> Unit) { /*...*/ } }
head, we can specify the title using a setter. So, HeadBuilder should have a title property. It also needs a function style in order to specify a style.class HeadBuilder { var title: String = "" fun style(body: String) { /*...*/ } }
body, which needs h1 and h3 methods. But what is +"Some text 1"? This is the unary plus operator on String[^10_2]. It’s strange, but we need it. A plain value would not work because we need a function call to add a value to a builder. This is why it’s become so common to use the unaryPlus operator in such cases.class BodyBuilder { fun h1(text: String) { /*...*/ } fun h3(text: String) { /*...*/ } operator fun String.unaryPlus() { /*...*/ } }
HeadBuilder, I just need to store the defined styles. We will use a list.class HeadBuilder { var title: String = "" private var styles: List<String> = emptyList() fun style(body: String) { styles += body } }
BodyBuilder, we need to keep the elements in order, so I will store them in a list, and I will use a dedicated classes to represent each view element type.class BodyBuilder { private var elements: List<BodyElement> = emptyList() fun h1(text: String) { this.elements += H1(text) } fun h3(text: String) { this.elements += H3(text) } operator fun String.unaryPlus() { elements += Text(this) } } sealed interface BodyElement data class H1(val text: String) : BodyElement data class H3(val text: String) : BodyElement data class Text(val text: String) : BodyElement
head and body, we need to do the same as we previously did in makeButton. There are typically three steps:- Create an empty builder.
- Fill it with data using the
initfunction. - Store it somewhere.
head could be implemented like this:fun head(init: HeadBuilder.() -> Unit) { val head = HeadBuilder() init.invoke(head) // or init(head) // or head.init() this.head = head }
apply. In head and body we store data in HtmlBuilder. In html we need to return the builder.fun html(init: HtmlBuilder.() -> Unit): HtmlBuilder { return HtmlBuilder().apply(init) } class HtmlBuilder { private var head: HeadBuilder? = null private var body: BodyBuilder? = null fun head(init: HeadBuilder.() -> Unit) { this.head = HeadBuilder().apply(init) } fun body(init: BodyBuilder.() -> Unit) { this.body = BodyBuilder().apply(init) } }
DslMarker and toString functions present our HTML as text.// DSL definition @DslMarker annotation class HtmlDsl @HtmlDsl fun html(init: HtmlBuilder.() -> Unit): HtmlBuilder { return HtmlBuilder().apply(init) } @HtmlDsl class HtmlBuilder { private var head: HeadBuilder? = null private var body: BodyBuilder? = null @HtmlDsl fun head(init: HeadBuilder.() -> Unit) { this.head = HeadBuilder().apply(init) } @HtmlDsl fun body(init: BodyBuilder.() -> Unit) { this.body = BodyBuilder().apply(init) } override fun toString(): String = listOfNotNull(head, body) .joinToString( separator = "", prefix = "<html>\n", postfix = "</html>", transform = { "$it\n" } ) } @HtmlDsl class HeadBuilder { var title: String = "" private var cssList: List<String> = emptyList() @HtmlDsl fun css(body: String) { cssList += body } override fun toString(): String { val css = cssList.joinToString(separator = "") { "<style>$it</style>\n" } return "<head>\n<title>$title</title>\n$css</head>" } } @HtmlDsl class BodyBuilder { private var elements: List<BodyElement> = emptyList() @HtmlDsl fun h1(text: String) { this.elements += H1(text) } @HtmlDsl fun h3(text: String) { this.elements += H3(text) } operator fun String.unaryPlus() { elements += Text(this) } override fun toString(): String { val body = elements.joinToString(separator = "\n") return "<body>\n$body\n</body>" } } sealed interface BodyElement data class H1(val text: String) : BodyElement { override fun toString(): String = "<h1>$text</h1>" } data class H3(val text: String) : BodyElement { override fun toString(): String = "<h3>$text</h3>" } data class Text(val text: String) : BodyElement { override fun toString(): String = text } // DSL usage val html = html { head { title = "My website" css("Some CSS1") css("Some CSS2") } body { h1("Title") h3("Subtitle 1") +"Some text 1" h3("Subtitle 2") +"Some text 2" } } fun main() { println(html) } /* <html> <head> <title>My website</title> <style>Some CSS1</style> <style>Some CSS2</style> </head> <body> <h1>Title</h1> <h3>Subtitle 1</h3> Some text 1 <h3>Subtitle 2</h3> Some text 2 </body> </html> */
- complicated data structures,
- hierarchical structures,
- a huge amount of data.
[^10_2]: Operators are better described in Kotlin Essentials, Operators chapter.
[^10_3]: A dialog (as Wikipedia explains) is a graphical control element in the form of a small window that communicates information to the user and prompts for a response.
[^10_4]: Concretely, when it is used as a receiver in a function type with a receiver, then it can only be used implicitly when it is the most inner receiver (so when it is an outer receiver, it needs to be used with an explicit
this@label).