的确,似乎几乎没有关于接收器概念的现有文件(只有small side note related to extension functions个),这是令人惊讶的:
with
, which given no knowledge of receivers might look like a keyword;All these topics have documentation, but nothing goes in-depth on receivers.
第一:
Kotlin中的任何代码块都可能有一个类型(甚至多个类型)作为receiver,使接收器的功能和属性在该代码块中可用,而无需对其进行限定.
想象一下这样一段代码:
{ toLong() }
Doesn't make much sense, right? In fact, assigning this to a function type of (Int) -> Long
- where Int
is the (only) parameter, and the return type is Long
- would rightfully result in a compilation error. You can fix this by simply qualifying the function call with the implicit single parameter it
. However, for DSL building, this will cause a bunch of issues:
html { it.body { // how to access extensions of html here? } ... }
it
个调用,特别是对于经常使用参数(即将成为接收方)的lambda.这就是receivers开始发挥作用的地方.
通过将这段代码分配给一个函数类型,该函数类型将Int
作为receiver(而不是参数!),代码突然编译:
val intToLong: Int.() -> Long = { toLong() }
Whats going on here?
This topic assumes familiarity with function types, but a little side note for receivers is needed.
Function types can also have one receiver, by prefixing it with the type and a dot. Examples:
Int.() -> Long // taking an integer as receiver producing a long
String.(Long) -> String // taking a string as receiver and long as parameter producing a string
GUI.() -> Unit // taking an GUI and producing nothing
Such function types have their parameter list prefixed with the receiver type.
It is actually incredibly easy to understand how blocks of code with receivers are handled:
Imagine that, similar to extension functions, the block of code is evaluated inside the class of the receiver type. 100 effectively becomes amended by the receiver type.
对于我们前面的示例val intToLong: Int.() -> Long = { toLong() }
,它有效地导致代码块在不同的上下文中进行计算,就像它被放置在Int
中的函数中一样.下面是一个使用手工制作的类型的不同示例,更好地展示了这一点:
class Bar
class Foo {
fun transformToBar(): Bar = TODO()
}
val myBlockOfCodeWithReceiverFoo: (Foo).() -> Bar = { transformToBar() }
effectively becomes (in the mind, not code wise - you cannot actually extend classes on the JVM):
class Bar
class Foo {
fun transformToBar(): Bar = TODO()
fun myBlockOfCode(): Bar { return transformToBar() }
}
val myBlockOfCodeWithReceiverFoo: (Foo) -> Bar = { it.myBlockOfCode() }
Notice how inside of a class, we don't need to use this
to access transformToBar
- the same thing happens in a block with a receiver.
碰巧的是,this上的文档还解释了如果当前代码块有两个接收器,如何通过qualified this使用最外层的接收器.
Yes. A block of code can have multiple receivers, but this currently has no expression in the type system. The only way to achieve this is via multiple higher-order functions that take a single receiver function type. Example:
class Foo
class Bar
fun Foo.functionInFoo(): Unit = TODO()
fun Bar.functionInBar(): Unit = TODO()
inline fun higherOrderFunctionTakingFoo(body: (Foo).() -> Unit) = body(Foo())
inline fun higherOrderFunctionTakingBar(body: (Bar).() -> Unit) = body(Bar())
fun example() {
higherOrderFunctionTakingFoo {
higherOrderFunctionTakingBar {
functionInFoo()
functionInBar()
}
}
}
Do note that if this feature of the Kotlin language seems inappropriate for your DSL, @DslMarker is your friend!
Why does all of this matter? With this knowledge:
toLong()
,而不必以某种方式引用这个数字.Maybe your extension function shouldn't be an extension?with
, a standard library function and not a keyword, exists - the act of amending the scope of a block of code to save on redundant typing is so common, the language designers put it right in the standard library.