How is it related to extension functions? Why is with
a function, not a keyword?
There appears to be no explicit documentation for this topic, only the assumption of knowledge in reference to extensions.
How is it related to extension functions? Why is with
a function, not a keyword?
There appears to be no explicit documentation for this topic, only the assumption of knowledge in reference to extensions.
It is true that there appears to be little existing documentation for the concept of receivers (only a small side note related to extension functions), which is surprising given:
with
, which given no knowledge of receivers might look like a keyword;All these topics have documentation, but nothing goes in-depth on receivers.
First:
Any block of code in Kotlin may have a type (or even multiple types) as a receiver, making functions and properties of the receiver available in that block of code without qualifying it.
Imagine a block of code like this:
{ 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
calls, especially for lambdas that use their parameter (soon to be receiver) a lot.This is where receivers come into play.
By assigning this block of code to a function type that has Int
as a receiver (not as a parameter!), the code suddenly compiles:
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. this effectively becomes amended by the receiver type.
For our earlier example, val intToLong: Int.() -> Long = { toLong() }
, it effectively results in the block of code being evaluated in a different context, as if it was placed in a function inside Int
. Here's a different example using handcrafted types that showcases this better:
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.
It just so happens that the documentation on this also explains how to use an outermost receiver if the current block of code has two receivers, via a 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()
in an extension function on a number, instead of having to reference the number somehow. 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.(Foo).() -> Unit
as a function that takes a Foo
as a receiver and no parameter. If that's true, how come you're invoking it with an argument Foo()
? –
Citadel { it: Foo -> it.first(); it.second(); it.etc(); }
you just write { first(); second(); etc(); }
and it
is "magically" inserted. The first - a conventional lambda - has the notation (Foo) -> {}
, while the "Magic Lambda" has the notation Foo.() -> {}
because the parameter's presence is only implied. –
Multi When you call:
"Hello, World!".length()
the string "Hello, World!"
whose length you're trying to get is called the receiver.
More generally, any time you write someObject.someFunction()
, with a .
between the object and the function name, the object is acting as the receiver for the function. This isn't special to Kotlin, and is common to many programming languages that use objects. So the concept of a receiver is likely very familiar to you, even if you haven't heard the term before.
It's called a receiver because you can think of the function call as sending a request which the object will receive.
Not all functions have a receiver. For example, Kotlin's println()
function is a top-level function. When you write:
println("Hello, World!")
you don't have to put any object (or .
) before the function call. There's no receiver because the println()
function doesn't live inside an object.
Now let's look at what a function call looks like from the point of view of the receiver itself. Imagine we've written a class that displays a simple greeting message:
class Greeter(val name: String) {
fun displayGreeting() {
println("Hello, ${this.name}!")
}
}
To call displayGreeting()
, we first create an instance of Greeter
, then we can use that object as a receiver to call the function:
val aliceGreeter = Greeter("Alice")
val bobGreeter = Greeter("Bob")
aliceGreeter.displayGreeting() // prints "Hello, Alice!"
bobGreeter.displayGreeting() // prints "Hello, Bob!"
How does the displayGreeting
function know which name to display each time? The answer is the keyword this
, which always refers to the current receiver.
aliceGreeter.displayGreeting()
, the receiver is aliceGreeter
, so this.name
points to "Alice"
.bobGreeter.displayGreeting()
, the receiver is bobGreeter
, so this.name
points to "Bob"
.Most of the time, there's actually no need to write this
. We can replace this.name
with just name
and it will implicitly point to the name
property of the current receiver.
class Greeter(val name: String) {
fun displayGreeting() {
println("Hello, $name!")
}
}
Notice how that differs from accessing a property from outside the class. To print the name from outside, we'd have to write out the full name of the receiver:
println("Hello, ${aliceGreeter.name}")
By writing the function inside the class, we can omit the receiver completely, making the whole thing much shorter. The call to name
still has a receiver, we just didn't have to write it out. We can say that we accessed the name
property using an implicit receiver.
Member functions of a class often need to access many other functions and properties of their own class, so implicit receivers are very useful. They shorten the code and can make it easier to read and write.
So far, it seems like a receiver is doing two things for us:
What if we want to write a function that can use an implicit receiver for convenient access to the properties and functions of an object, but we don't want to (or can't) write our new function inside that object/class? This is where Kotlin's extension functions come in.
fun Greeter.displayAnotherGreeting() {
println("Hello again, $name!")
}
This function doesn't live inside Greeter
, but it accesses Greeter
as if it was a receiver. Notice the receiver type before the function name, which tells us that this is an extension function. In the body of the extension function, we can once again access name
without its receiver, even though we're not actually inside the Greeter
class.
You could say that this isn't a "real" receiver, because we're not actually sending the function call to an object. The function lives outside the object. We're just using the syntax and appearance of a receiver because it makes for convenient and concise code. We can call this an extension receiver, to distinguish it from the dispatch receiver that exists for functions that are really inside an object.
Extension functions are called in the same way as member functions, with a receiver object before the function name.
val aliceGreeter = Greeter("Alice")
aliceGreeter.displayAnotherGreeting() // prints "Hello again, Alice!"
Because the function is always called with an object in the receiver position before the function name, it can access that object using the keyword this
. Like a member function, an extension function can also leave out this
and access the receiver's other properties and functions using the current receiver instance as the implicit receiver.
One of the main reasons extension functions are useful is that the current extension receiver instance can be used as an implicit receiver inside the body of the function.
with
do?So far we've seen two ways to make something available as an implicit receiver:
Both approaches require creating a function. Can we have the convenience of an implicit receiver without declaring a new function at all?
The answer is to call with
:
with(aliceGreeter) {
println("Hello again, $name!")
}
Inside the block body of the call to with(aliceGreeter) { ... }
, aliceGreeter
is available as an implicit receiver and we can once again access name
without its receiver.
So how come with
can be implemented as a function, rather than a language feature? How is it possible to simply take an object and magic it into an implicit receiver?
The answer lies with lambda functions. Let's consider our displayAnotherGreeting
extension function again. We declared it as a function, but we could instead write it as a lambda:
val displayAnotherGreeting: Greeter.() -> Unit = {
println("Hello again, $name!")
}
We can still call aliceGreeter.displayAnotherGreeting()
the same as before, and the code inside the function is the same, complete with implicit receiver. Our extension function has become a lambda with receiver. Note the way the Greeter.() -> Unit
function type is written, with the extension receiver Greeter
listed before the (empty) parameter list ()
.
Now, watch what happens when we pass this lambda function as an argument to another function:
fun runLambda(greeter: Greeter, lambda: Greeter.() -> Unit) {
greeter.lambda()
}
The first argument is the object that we want to use as the receiver. The second argument is the lambda function we want to run. All runLambda
does is to call the provided lambda parameter, using the greeter
parameter as the lambda's receiver.
Substituting the code from our displayAnotherGreeting
lambda function into the second argument, we can call runLambda
like this:
runLambda(aliceGreeter) {
println("Hello again, $name!")
}
And just like that, we've turned aliceGreeter
into an implicit receiver. Kotlin's with
function is simply a generic version of this that works with any type.
someObject.someFunction()
, someObject
is acting as the receiver that receives the function callsomeFunction
, someObject
is "in scope" as the current receiver instance, and can be accessed as this
this
and access its properties and functions using an implicit receiverwith
function uses a lambda with receiver to make receivers available anywhere, not just inside member functions and extension functionsKotlin knows the concept of a function literals with receiver. It enables access on visible methods and properties of a receiver of a lambda within its body without having to use any additional qualifier. That's very similar to extension functions in which you can as well access members of the receiver object inside the extension.
A simple example, also one of the greatest functions in the Kotlin standard library, is apply
:
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
Here, block
is a function literal with receiver. This block parameter is executed by the function and the receiver of apply, T
, is returned to the caller. In action this looks as follows:
val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}
We instantiate an object of Bar
and call apply
on it. The instance of Bar
becomes the receiver of apply
. The block
, passed as an argument in curly brackets does not need to use additional qualifiers to access and modify the properties color
and text
.
The concept of lambdas with receiver is also the most important feature for writing DSLs with Kotlin.
{...}
in apply{...}
is just the lambda function as argument to apply
. The lambda is a trailing lambda, where it doesn't have to be in the apply parentheses. It could actually be apply({...}), which would be less confusing for me when first learning this. kotlinlang.org/docs/reference/… –
Gerrigerrie var greet: String.() -> Unit = { println("Hello $this") }
this defines a variable of type String.() -> Unit
, which tells you
String
is the receiver () -> Unit
is the function typeLike F. George mentioned above, all methods of this receiver can be called in the method body.
So, in our example, this
is used to print the String
. The function can be invoked by writing...
greet("Fitzgerald") // result is "Hello Fitzgerald"
the above code snippet was taken from Kotlin Function Literals with Receiver – Quick Introduction by Simon Wirtz.
greet
is defined as a method that has a String
receiver but no parameters. So I understand how we can call "Fitzgerald".greet()
, but how can we call greet("Fitzgerald")
? –
Caryl Simply put ( without any extra words or complications) , the "Receiver" is the type being extended in the extension function or the class name. Using the examples given in answers above
fun Foo.functionInFoo(): Unit = TODO()
Type "Foo" is the "Receiver"
var greet: String.() -> Unit = { println("Hello $this") }
Type "String" is the "Receiver"
Additional tip: Look out for the Class before the fullstop(.) in the "fun" (function) declaration
fun receiver_class.function_name() {
//...
}
Simply put:
this
keyword inside the function body corresponds to the receiver objectAn extension function example:
// `Int` is the receiver type
// `this` is the receiver object
fun Int.squareDouble() = toLong() * this
// a receiver object `8` of type `Int` is passed to the `square` function
val result = 8.square()
A function literal example, which is pretty much the same:
// `Int` is the receiver type
// `this` is the receiver object
val square: Int.() -> Long = { toLong() * this }
// a receiver object `8` of type `Int` is passed to the `square` function
val result1 = 8.square()
val result2 = square(8) // this call is equal to the previous one
The object instance before the . is the receiver. This is in essence the "Scope" you will define this lambda within. This is all you need to know, really, because the functions and properties(varibles, companions e.t.c) you will be using in the lambda will be those provided within this scope.
class Music(){
var track:String=""
fun printTrack():Unit{
println(track)
}
}
//Music class is the receiver of this function, in other words, the lambda can be piled after a Music class just like its extension function Since Music is an instance, refer to it by 'this', refer to lambda parameters by 'it', like always
val track_name:Music.(String)->Unit={track=it;printTrack()}
/*Create an Instance of Music and immediately call its function received by the name 'track_name', and exclusively available to instances of this class*/
Music().track_name("Still Breathing")
//Output
Still Breathing
You define this variable with and all the parameters and return types it will have but among all the constructs defined, only the object instance can call the var, just like it would an extension function and supply to it its constructs, hence "receiving" it. A receiver would hence be loosely defined as an object for which an extension function is defined using the idiomatic style of lambdas.
Typically in Java or Kotlin you have methods or functions with input parameters of type T. In Kotlin you can also have extension functions that receive a value of type T.
If you have a function that accepts a String parameter for example:
fun hasWhitespace(line: String): Boolean {
for (ch in line) if (ch.isWhitespace()) return true
return false
}
converting the parameter to a receiver (which you can do automatically with IntelliJ):
fun String.hasWhitespace(): Boolean {
for (ch in this) if (ch.isWhitespace()) return true
return false
}
we now have an extension function that receives a String and we can access the value with this
© 2022 - 2024 — McMap. All rights reserved.