Reputation: 43
I'm trying to understand the following code (source).
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}
What I really cannot grasp is the line
html.init() // pass the receiver object to the lambda
What is happening here?
Can somebody please explain in simple words what is going on here?
Upvotes: 1
Views: 270
Reputation: 29844
First, let's make this example a little easier and see what the problem is then.
We could build the html
function like this:
fun html(init: (HTML) -> Unit): HTML {
val html = HTML()
init(html)
return html
}
This would be easier to grasp (at first), because we are just passing a usual one-parameter lambda to the html
function.
But now the call-site is not builder like:
html { it: HTML -> // just for clarity
it.body() // not very nice
}
Wouldn't it be nice if we could invoke body()
inside html
without it
? That's possible! All we need is a lambda with receiver.
fun html(init: HTML.() -> Unit): HTML { // <-- only this changed
val html = HTML()
init(html)
return html
}
See how html
is passed as an argument to init
like before?
Of course, we can invoke it like this too: html.init()
as shown in the example. The instance of HTML
becomes this
inside the block of the lambda.
Now, we can do this:
html {
this.body()
}
Since this
can be omitted, we arrive here:
html {
body()
}
So, in the end lambdas with receivers make the code more concise and allow us to use a nice builder syntax.
Upvotes: 3
Reputation: 8096
Here is step by step explaination:
1. Creation of function, receiver type lambda.
fun html(init: HTML.() -> Unit): HTML {
here function html accept a parameter init
of type HTML.() -> Unit
i.e. it indicated that it is a receiver of HTML and can only be called with help of a real HTML object. And : HTML
indicates that the function obviously returns HTML object.
2. call of init at html
html.init()
Here init() function is called as a receiver of HTML by a real HTML object.
Alright enough of formal talking, Here is what a receiver is:
So if you remember extension function defined as fun A.myFun(...): ReturnType {}
, in that case you get a variable this
which act as an instance of type A it was called on.
Similarly receiver lambda gives you a this
variable inside that,
In a particular example:
class A {
fun thisCanBeCalledByAInstance() {
println("I've got called")
}
}
fun main() {
val receiver: A.() -> Unit = { // this: A
thisCanBeCalledByAInstance() // prints: I've got called
// or traditional way this.thisCanBeCalledByAInstance()
}
val a: A = A()
a.receiver()
}
Here you were able to call the method(function) from the instance of A
even if it was lambda because it was a receiver.
PS: For simple langauge you can think html.init() as init(html) but html is not a parameter but instead works as this
vaiable inside the lambda
This is why you were able to call body()
on that lambda, because implicitly you were calling this.body()
and this
has came from html.init()
's html object.
Upvotes: 1