We’ve seen extension methods:
Type.extensionMethod() {
doStuffWith(this)
}
And we’ve seen lambdas:
fun doLongRunningTask(onDone: () -> Unit) {
figureOutMeaningOfLife()
onDone()
}
What happens if we put those two syntax elements together? The answer is we get a Kotlin feature called a function literal with receiver. (But if that’s too much of a mouthful you can think of it as an “extension lambda”.)
Here’s an example:
fun html(provideContent: Html.() -> Unit): Html {
val html = Html()
html.provideContent()
return html
}
There’s a lot going on there so let’s unpack it piece-by-piece.
html() is a function declared at global scope.html() returns an object of type Html.html() takes a single argument, provideContent, which is a lambda.provideContent is slightly different to lambdas we’ve seen before. As well as the usual lambda syntax () -> Unit (no arguments, returns Unit), it’s also scoped to the Html class, using the same syntax as an extension method: Html.() -> UnitprovideContent lambda (just like an extension method) is scoped to the Html object it’s called on. In other words, inside the lambda, this refers to an Html object.html() function is pretty standard Kotlin now: it creates a new Html object, invokes the provideContent lambda on it as if it’s a method of the Html class, then returns the (now initialised) object.The lambda is the function literal, and the Html class is the receiver, hence function literal with receiver.
Let’s imagine our Html class is very basic indeed and looks like this:
class Html {
private val contentBuilder = StringBuilder()
fun div(innerText: String) = contentBuilder.appendLine("<div>$innerText</div>")
override fun toString() = "<html>\n$contentBuilder</html>\n"
}
And now let’s put all that together and see how the html function is called:
val myHtml = html {
// We're now inside the lambda body:
// `this` refers to an Html object
div("Hello world") // Html class method call
div("Goodbye world") // And another
}
println(myHtml)
// Output:
// <html>
// <div>Hello world</div>
// <div>Goodbye world</div>
// </html>
Look! We’ve built a very simple DSL! This is the exact equivalent of writing this:
val myHtml = Html()
myHtml.div("Hello world")
myHtml.div("Goodbye world")
println(myHtml)