我正在尝试为带有 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
工作正常,并且会同时改变age
和hasDog
属性。然而,它不是可以按照函数暗示的方式应用的 lambda(user1.lambda()
即 lambda 中的 2 行没有循环)。
官方文档用以下方式定义了这些带有接收器的 lambda:“该类型A.(B) -> C
表示可以在接收器对象上调用的A
函数,其参数为B
并返回值C
。”
我知道 theage
和 the可以单独hasDog
应用于,如 in ,并且语法糖允许我们在 lambda 声明中省略and 。但是,我怎样才能协调语法和这两者都将按顺序运行的事实!函数声明中的任何内容都不会让我相信将一一应用上的事件。user1
user1.age = 42
this.age
this.hasDog
config()
user1
那只是“它是怎样的”,以及某种语法糖,我应该学会这样阅读它们(我的意思是我可以看到它在做什么,我只是不太从语法中得到它),还是还有更多正如我想象的那样,这一切都通过我不太了解的其他魔法以一种美丽的方式结合在一起?