0

我正在尝试为带有 Kotlin 接收器的 lambdas 以及 DSL 的工作原理建立一个良好的心智模型。简单的很容易,但我的心智模型会因复杂的而分崩离析。

第1部分

假设我们有一个changeVolume如下所示的函数:

fun changeVolume(operation: Int.() -> Int): Unit {
    val volume = 10.operation()
}

我将大声描述此功能的方式如下:

函数 changeVolume 采用必须适用于 Int(接收器)的 lambda。此 lambda 不接受任何参数,并且必须返回一个 Int。根据 10.lambdaPassedToFunction() 表达式,传递给 changeVolume 的 lambda 将应用于 Int 10。

然后我会使用类似下面的方法调用这个函数,突然之间我们有了一个小型 DSL 的开始:

changeVolume {
    plus(100)
}

changeVolume {
    times(2)
}

这很有意义,因为传递的 lambda 直接适用于 any Int,我们的函数只是在内部使用它(比如10.plus(100), or 10.times(2)

第2部分

但举一个更复杂的例子:

data class UserConfig(var age: Int = 0, var hasDog: Boolean = true)
val user1: UserConfig = UserConfig()

fun config(lambda: UserConfig.() -> Unit): Unit {
    user1.lambda()
}

在这里,我们再次看到了一个看似简单的函数,我很想向朋友描述它为“传递一个可以有一个UserConfig类型作为接收器的 lambda,它会简单地将该 lambda 应用于user1”。

但请注意,我们可以将看似非常奇怪的 lambdas 传递给该函数,它们会正常工作:

config {
    age = 42
    hasDog = false
}

对上面的调用config工作正常,并且会同时改变agehasDog属性。然而,它不是可以按照函数暗示的方式应用的 lambda(user1.lambda()即 lambda 中的 2 行没有循环)。

官方文档用以下方式定义了这些带有接收器的 lambda:“该类型A.(B) -> C表示可以在接收器对象上调用的A函数,其参数为B并返回值C。”

我知道 theage和 the可以单独hasDog应用于,如 in ,并且语法糖允许我们在 lambda 声明中省略and 。但是,我怎样才能协调语法和这两者都将按顺序运行的事实!函数声明中的任何内容都不会让我相信将一一应用上的事件。user1 user1.age = 42this.agethis.hasDogconfig()user1

那只是“它是怎样的”,以及某种语法糖,我应该学会这样阅读它们(我的意思是我可以看到它在做什么,我只是不太从语法中得到它),还是还有更多正如我想象的那样,这一切都通过我不太了解的其他魔法以一种美丽的方式结合在一起?

4

1 回答 1

2

lambda 就像任何其他函数一样。你没有循环通过它。您调用它,它会按顺序从第一行到返回语句运行其逻辑(尽管return不允许使用裸关键字)。lambda 的最后一个表达式被视为返回语句。如果您没有将参数定义为接收器,而是将其定义为标准参数,如下所示:

fun config(lambda: (UserConfig) -> Unit): Unit {
    user1.lambda()
}

那么相当于你上面的代码将是

config { userConfig ->
    userConfig.age = 42
    userConfig.hasDog = false
}

您还可以将使用传统语法编写的函数传递给这个高阶函数。Lambda 只是它的不同语法。

fun changeAgeAndRemoveDog(userConfig: UserConfig): Unit {
    userConfig.age = 42
    userConfig.hasDog = false
}

config(::changeAgeAndRemoveDog) // equivalent to your lambda code

或者

config(
    fun (userConfig: UserConfig): Unit {
        userConfig.age = 42
        userConfig.hasDog = false
    }
)

或者回到你原来的例子 B 部分,你可以把任何你想要的逻辑放在 lambda 中,因为它就像任何其他函数一样。你不必对接收器做任何事情,或者你可以用它做各种各样的事情,也可以做不相关的事情。

config {
    age = 42
    println(this) // prints the toString of the UserConfig receiver instance
    repeat(3) { iteration ->
        println(copy(age = iteration * 4)) // prints copies of receiver
    }
    (1..10).forEach {
        println(it)
        if (it == 5) {
            println("5 is great!")
        }
    }
    hasDog = false
    println("I return Unit.")
}
于 2021-04-06T13:06:53.547 回答