Much of the power of DSLs in Kotlin comes from their type safety. When we call the html function we've been examining, we pass in a function argument with an HTML receiver. Because of Kotlin's lambda syntax, we can then move the passed function outside of the parentheses, thereby making the code easier to read and write:
val result = html {
...
}
In the following snippet, we can see how the init argument is defined as a function type with a receiver of the HTML type. To indicate a function type with a receiver, we must first reference the type and then add a .(). So, in this case, HTML.() indicates a function type returning Unit with an HTML receiver:
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
We can then pass that init argument to the function using lambda syntax. Within that passed lambda, we can access properties and call methods from the HTML receiver, as follows:
val result = html {
head {
...
}
body {
...
}
In this case, the head and body function calls are methods on the HTML class:
class HTML : TagWithText("html") {
fun head(init: Head.() -> Unit) = initTag(Head(), init)
fun body(init: Body.() -> Unit) = initTag(Body(), init)
}
Within the lambda block, the implicit reference to the HTML receiver will reference the new HTML instance that was created within the HTML function:
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
However, any HTML instance can be used, as in the case of the extension function we looked at previously. In this case, instead of creating a new instance of HTML, we can use the implicit reference of the extension function to access an instance of HTML:
fun HTML.html(init: HTML.() -> Unit): HTML {
this.init()
return this
}
By leveraging function types with receivers, we can make our functions composable and configurable with static type safety.