Kotlin 接收器函数用于 DSL 所需的解释

Explanation needed for Kotlin receiver function use for DSL

我正在尝试理解以下代码 (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
}

我实在是抓不住线

html.init() // pass the receiver object to the lambda

这里发生了什么?

谁能简单解释一下这是怎么回事?

这里是一步一步的解释:

1.创建函数,接收者类型 lambda。

fun html(init: HTML.() -> Unit): HTML {

这里的函数 html 接受类型为 HTML.() -> Unit 的参数 init 即表示它是 HTML 的接收者并且只能在真正的 HTML 对象。而: HTML表示函数明明是returnsHTMLobject.

2。在 html

调用 init
html.init()

这里 init() 函数被真正的 HTML 对象作为 HTML 的接收者调用。


好吧,正式的谈话,这是接收器是什么:

因此,如果您还记得定义为 fun A.myFun(...): ReturnType {} 的扩展函数,那么您会得到一个变量 this,它充当调用它的类型 A 的实例。

同样地,接收器 lambda 给你一个 this 变量,

在一个特定的例子中:

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()
}

这里您可以从 A 的实例调用方法(函数),即使它是 lambda,因为它是一个接收器。

PS:对于简单的语言,您可以将 html.init() 视为 init(html) 但 html 不是参数而是在 lambda

中作为 this 变量工作

这就是为什么您能够在该 lambda 上调用 body(),因为隐含地您正在调用 this.body() 并且 this 来自 html.init() 的 html对象。

首先,让我们把这个例子简单一点,然后看看问题出在哪里。

我们可以像这样构建 html 函数:

fun html(init: (HTML) -> Unit): HTML {
    val html = HTML()
    init(html)
    return html
}

这会更容易理解(一开始),因为我们只是将一个普通的单参数 lambda 传递给 html 函数。

但现在调用站点不是像这样的构建器:

html { it: HTML -> // just for clarity     
    it.body() // not very nice
}

如果我们可以在没有 it 的情况下在 html 中调用 body() 不是很好吗?这是可能的!我们只需要一个带有接收器的 lambda。

fun html(init: HTML.() -> Unit): HTML { // <-- only this changed
    val html = HTML()
    init(html)
    return html
}

看看 html 是如何像以前一样作为参数传递给 init 的? 当然,我们也可以这样调用它: html.init() 如示例所示。 HTML 的实例在 lambda 块内变为 this

现在,我们可以这样做:

html {      
   this.body()
}

由于this可以省略,所以我们得出:

html {      
   body()
}

因此,最终带有接收器的 lambda 使代码更加简洁,并允许我们使用一个很好的构建器语法。