Kotlin lambdas with receivers:寻求对我心智模型的澄清
Kotlin lambdas with receivers: seeking clarification on my mental model
我正在尝试使用 Kotlin 中的接收器为 lambda 建立一个良好的心智模型,以及 DSL 的工作原理。简单的很容易,但复杂的我的心智模型就崩溃了。
第 1 部分
假设我们有一个函数 changeVolume
如下所示:
fun changeVolume(operation: Int.() -> Int): Unit {
val volume = 10.operation()
}
我将大声描述此功能的方式如下:
A function changeVolume takes a lambda that must be applicable to an Int (the receiver). This lambda takes no parameters and must return an Int. The lambda passed to changeVolume will be applied to the Int 10, as per the 10.lambdaPassedToFunction() expression.
然后我会使用类似下面的方法调用这个函数,突然之间我们有了一个小型 DSL 的开始:
changeVolume {
plus(100)
}
changeVolume {
times(2)
}
这很有意义,因为传递的 lambda 直接适用于任何 Int
,我们的函数只是在内部使用它(比如 10.plus(100)
,或 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
".
但请注意,我们可以将看似非常奇怪的 lambda 传递给该函数,它们会正常工作:
config {
age = 42
hasDog = false
}
上面对 config
的调用工作正常,并且会更改 age
和 hasDog
属性。然而,它不是可以按照函数暗示的方式应用的 lambda(user1.lambda()
,即 lambda 中的两行没有循环)。
官方文档通过以下方式定义那些带有接收器的 lambda:“类型 A.(B) -> C
表示可以在 A
[=68= 的接收器对象上调用的函数 ] 参数为 B
和 return 值为 C
。"
我知道 age
和 hasDog
可以分别应用于 user1
,如 user1.age = 42
,而且语法糖允许我们在 lambda 声明中省略 this.age
和 this.hasDog
。但是我怎样才能协调语法和这两个将是 运行 的事实呢! config()
的函数声明中的任何内容都不会让我相信 user1
上的事件将一一应用。
这只是“它是怎样的”吗,以及某种语法糖,我应该学会这样阅读它们(我的意思是我可以看到它在做什么,我只是不太了解 它来自语法),或者还有更多,正如我想象的那样,这一切都通过我不太了解的其他魔法以一种美丽的方式组合在一起?
lambda 与任何其他函数一样。你不是在循环它。您调用它,它从第一行到 return 语句按顺序运行其逻辑(尽管不允许裸露的 return
关键字)。 lambda 的最后一个表达式被视为 return 语句。如果您没有将参数定义为接收者,而是像这样定义为标准参数:
fun config(lambda: (UserConfig) -> Unit): Unit {
user1.lambda()
}
那么你上面的代码相当于
config { userConfig ->
userConfig.age = 42
userConfig.hasDog = false
}
您也可以将使用传统语法编写的函数传递给此高阶函数。 Lambdas 只是它的一种不同的语法。
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.")
}
我正在尝试使用 Kotlin 中的接收器为 lambda 建立一个良好的心智模型,以及 DSL 的工作原理。简单的很容易,但复杂的我的心智模型就崩溃了。
第 1 部分
假设我们有一个函数 changeVolume
如下所示:
fun changeVolume(operation: Int.() -> Int): Unit {
val volume = 10.operation()
}
我将大声描述此功能的方式如下:
A function changeVolume takes a lambda that must be applicable to an Int (the receiver). This lambda takes no parameters and must return an Int. The lambda passed to changeVolume will be applied to the Int 10, as per the 10.lambdaPassedToFunction() expression.
然后我会使用类似下面的方法调用这个函数,突然之间我们有了一个小型 DSL 的开始:
changeVolume {
plus(100)
}
changeVolume {
times(2)
}
这很有意义,因为传递的 lambda 直接适用于任何 Int
,我们的函数只是在内部使用它(比如 10.plus(100)
,或 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
".
但请注意,我们可以将看似非常奇怪的 lambda 传递给该函数,它们会正常工作:
config {
age = 42
hasDog = false
}
上面对 config
的调用工作正常,并且会更改 age
和 hasDog
属性。然而,它不是可以按照函数暗示的方式应用的 lambda(user1.lambda()
,即 lambda 中的两行没有循环)。
官方文档通过以下方式定义那些带有接收器的 lambda:“类型 A.(B) -> C
表示可以在 A
[=68= 的接收器对象上调用的函数 ] 参数为 B
和 return 值为 C
。"
我知道 age
和 hasDog
可以分别应用于 user1
,如 user1.age = 42
,而且语法糖允许我们在 lambda 声明中省略 this.age
和 this.hasDog
。但是我怎样才能协调语法和这两个将是 运行 的事实呢! config()
的函数声明中的任何内容都不会让我相信 user1
上的事件将一一应用。
这只是“它是怎样的”吗,以及某种语法糖,我应该学会这样阅读它们(我的意思是我可以看到它在做什么,我只是不太了解 它来自语法),或者还有更多,正如我想象的那样,这一切都通过我不太了解的其他魔法以一种美丽的方式组合在一起?
lambda 与任何其他函数一样。你不是在循环它。您调用它,它从第一行到 return 语句按顺序运行其逻辑(尽管不允许裸露的 return
关键字)。 lambda 的最后一个表达式被视为 return 语句。如果您没有将参数定义为接收者,而是像这样定义为标准参数:
fun config(lambda: (UserConfig) -> Unit): Unit {
user1.lambda()
}
那么你上面的代码相当于
config { userConfig ->
userConfig.age = 42
userConfig.hasDog = false
}
您也可以将使用传统语法编写的函数传递给此高阶函数。 Lambdas 只是它的一种不同的语法。
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.")
}