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.() -> Unit
provideContent
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)