'How to create an extension function with multiple receivers in Kotlin?
I want my extension function to have a couple of receivers. For example, I want function handle
to be able to call methods of both CoroutineScope
and Iterable
instances:
fun handle() {
// I want to call CoroutineScope.launch() and Iterable.map() functions here
map {
launch { /* ... */ }
}
}
I thought this might work:
fun <T> (Iterable<T>, CoroutineScope).handle() {}
But it gives me an error:
Function declaration must have a name
I know that I can create the function with parameters, but
Is it possible to have multiple receivers for a single function and how to do that without parameters?
Solution 1:[1]
In the Kotlin version 1.6.20 there is a new feature called Context receivers. This is a first prototype of context receivers. This feature allows to make functions, properties and classes context-dependent by adding context receivers to their declaration. There is a new syntax for that. In front of the function declaration we can specify a list of contextual types that would be required to invoke this function. A contextual declaration does the following:
- It requires all declared context receivers to be present in a caller's scope as implicit receivers.
- It brings declared context receivers into the body scope of implicit receivers.
The solution with context receivers looks like the following:
context(CoroutineScope)
fun <T> Iterable<T>.handle() {
map {
launch { /* ... */ }
}
}
someCoroutineScope.launch {
val students = listOf(...)
students.handle()
}
In the context(CoroutineScope)
we can declare multiple types, e.g context(CoroutineScope, LogInterface)
.
Since context receivers feature is a prototype, to enable it add -Xcontext-receivers
compiler option in the app's build.gradle
file:
apply plugin: 'kotlin-android'
android {
//...
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += [
"-Xcontext-receivers"
]
}
}
Solution 2:[2]
As far as I know, this is currently impossible for types that we don't control. There are plans to add such feature, it is processed under KEEP-259.
I don't know what is the planned roadmap or when we could expect it to be added, but I hope we will see at least some previews this year.
Solution 3:[3]
This is a very narrow case, but if your use case is that you have a higher order function where you want code in the lambda to have multiple receivers, and if the types you're wanting to combine are interfaces, you can create a class that wraps the interfaces as delegates. Within the lambda passed to the below function, you can call both Iterable and CoroutineScope functions.
class CoroutineScopeAndIterable<T>(
private val coroutineScope: CoroutineScope,
private val iterable: Iterable<T>
): CoroutineScope by coroutineScope, Iterable<T> by iterable
suspend fun <T> CoroutineScope.runSomething(
iterable: Iterable<T>,
block: suspend CoroutineScopeAndIterable<T>.() -> Unit
) {
CoroutineScopeAndIterable(this, iterable).block()
}
Solution 4:[4]
Here is workaround you can use:
val <T> Iterable<T>.handle: CoroutineScope.() -> Unit get() = {
map {
launch { }
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 | Semyon |