Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 279
Sergey Avatar asked Nov 27 '25 03:11

Sergey


2 Answers

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"
        ]
    }
}

UPDATE MAY 22, 2025

Starting with Kotlin 2.0.20 context receivers will be gradually phased out and replaced with context parameters.

like image 101
Sergey Avatar answered Nov 28 '25 15:11

Sergey


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()
}
like image 31
Tenfour04 Avatar answered Nov 28 '25 17:11

Tenfour04



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!