Bernd Günther
Bernd Günther

Reputation: 43

Explanation needed for Kotlin receiver function use for DSL

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

Answers (2)

Willi Mentzel
Willi Mentzel

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

Animesh Sahu
Animesh Sahu

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

Related Questions