Can't reify a suspendable block passed to forEach - kotlin

Given some
suspend fun a(): Int
This works:
launch(Unconfined) {
(1..10).forEach {
val a = a()
println("Result is $a")
}
}
But this fails at compile time:
val action: (Int) -> Unit = {
// Suspend function should be called only from a coroutine
// or another suspend function:
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach(action)
}
Furthermore, it isn't fixable because:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
// suspend (Int) -> Unit cannot be applied to (T) -> Unit
(1..10).forEach(action)
}
What is the story here in terms of the static type system? The current situation looks like a quick hack where an inline block containing a suspend fun call is still inferred to a non-suspend type signature.
Is this an area where the design will be improved before being finalized?

The suspend and normal functional types are not subtypes of each other and thus cannot be assigned or passed to a function in place of each other:
val f: () -> Unit = { }
val g: suspend () -> Unit = f // Type mismatch
val f: suspend () -> Unit = { }
val g: () -> Unit = f // Type mismatch
This is why a suspend (Int) -> Unit cannot be passed to forEach.
Basically, the restriction for suspend functions to be called only in other suspend functions works irrespective to the type system. Such calls should simply be placed inside a suspend function or a suspend lambda or inlined into one. So, this should also work:
val action: suspend (Int) -> Unit = {
val a = a()
println("Result is $a")
}
launch(Unconfined) {
(1..10).forEach { action() } // The call is inlined into a suspend lambda
}
I've filed an issue about supporting (1..10).forEach(action) as well: KT-22186

Related

How to return a suspend function, which accepts an argument?

All of the following examples require a mutex variable.
val mutex = Mutex()
If I have an non-suspend function, I can use it create a synchronzied suspend function:
fun action0 () {}
suspend fun synchronizedAction0 () {
mutex.withLock {
action0 ()
}
}
The same applies to functions with arguments:
fun action1 (x: T) {}
suspend fun synchronizedAction1 (x: T) {
mutex.withLock {
action1 (x)
}
}
If I have more than one non-suspend function, I want to convert into synchronized versions, I can write a non-suspend function, which takes another non-suspend function and converts it into a suspend function.
fun synchronize (action0: () -> Unit): suspend () -> Unit {
return suspend {
mutex.withLock {
action0 ()
}
}
}
But how to do it for an action, which requires an argument?
I tried this, but it does not compile:
fun synchronize (action1: (T) -> Unit): suspend (T) -> Unit {
return suspend { x ->
mutex.withLock {
action1 (x)
}
}
}
The error is, that the compiler can not infer the type of parameter x. I think it is obvious, it is T. How can I tell Kotlin?
I tried this, but it does not compile either.
fun synchronize (action1: (T) -> Unit): suspend (T) -> Unit {
return suspend fun (x: T) {
mutex.withLock {
action1 (x)
}
}
}
What is the right syntax?
First of all, we need to define T as a type parameter for the synchronize() function. Then we don't really have to specify suspend or argument types for the lambda - they will be automatically inferred by the compiler:
fun <T> synchronize (action1: (T) -> Unit): suspend (T) -> Unit {
return { x ->
mutex.withLock {
action1 (x)
}
}
}

Add a side-effect to a function in a generic way

How can I write a Kotlin generic function that takes a function as an argument and adds a side-effect to it? For instance,
fun something(one: Int, two: String): String { return "${one}, ${two}" }
fun somethingElse(arg: Array<String>): String { return "${arg}" }
val w1 = wrapped(::something)
w1(42, "hello")
val w2 = wrapped(::somethingElse)
w2(arrayOf("ichi", "ni"))
The following works for functions that take only a single parameter:
fun <A, R> wrapped(theFun: (a: A) -> R): (a: A) -> R {
return { a: A ->
theFun(a).also { println("wrapped: result is $it") }
}
}
To make this work with an arbitrary number of arguments, I'd need some construct that gives me the type of the argument list. Unfortunately, the Function generic can't be used since it takes only one parameter. The following does not compile:
fun <A, R> wrapped(theFun: Function<A, R>): Function<A, R> {
return { args: A ->
theFun(*args).also { println("wrapped: result is ${it}") }
}
}
Or maybe I could use varargs? Does not seem to work with lambdas. Or Kotlin reflection?
Solution using reflection:
class KFunctionWithSideEffect<R>(private val f: KFunction<R>, private val sideEffect: (R) -> Unit) : KFunction<R> by f {
override fun call(vararg args: Any?) = f.call(*args).also { sideEffect(it) }
override fun callBy(args: Map<KParameter, Any?>) = f.callBy(args).also { sideEffect(it) }
}
fun <R> wrapped(theFun: KFunction<R>, sideEffect: (R) -> Unit = { str -> println("wrapped: result is $str") }) =
KFunctionWithSideEffect(theFun, sideEffect)
Usage:
val w1 = wrapped(::something)
w1.call(42, "hello")
val w2 = wrapped(::somethingElse)
w2.call(arrayOf("ichi", "ni"))

Kotlin: Apply a suspend function on a list "in parallel"?

If I have a List<A> and a function suspend (A) -> B, how can I apply this function on the list in parallel?
coroutineScope {
list.map {
async {
process(it)
}
} // List<Deferred<B>>
.awaitAll() // List<B>
}
suspend fun process(a: A): B {
...
}
This assumes you are already in a suspend context. Otherwise, you need to launch a new coroutine on the appropriate scope instead of using the coroutineScope scoping function.
You can create an extension function on CoroutineScope, go through each element of the list and launch a coroutine for each element. In this way elements of the list will be processed in parallel. Some code snippet:
fun CoroutineScope.processListInParallel(list: List<A>): List<Deferred<B>> = list.map {
async { // launch a coroutine
processA(it)
}
}
GlobalScope.launch {
val list = listOf(A("name1"), A("name2"), A("name3"))
val deferredList = processListInParallel(list)
val results: List<B> = deferredList.awaitAll() // wait for all items to be processed
}
suspend fun processA(a: A): B {
delay(1000) // emulate suspension
return B("Result ${a.name}")
}
data class A(val name: String) {}
data class B(val name: String) {}
Note: GlobalScope is used here as an example, using it is highly discouraged, application code usually should use an application-defined CoroutineScope.

Wrapping async code with suspendCoroutine vs sync code with suspending builder

I'm looking for suspending way writing to file. I found this example from Kotlin/coroutines-examples which is wraping AsynchronousFileChannel with suspendCoroutine { ... } but I'm wondering if there is any benefit from wrapping synchronous call with withContext(IO){}
private suspend fun File.writeTextAsync(text: String): Unit = suspendCoroutine { cont ->
val aFileChannel = AsynchronousFileChannel.open(toPath(), StandardOpenOption.WRITE)
val byteBuffer = ByteBuffer.wrap(text.toByteArray())
aFileChannel.write(
byteBuffer,
0,
Unit,
object : java.nio.channels.CompletionHandler<Int, Unit> {
override fun completed(bytesRead: Int, attachment: Unit) {
cont.resume(Unit)
}
override fun failed(exception: Throwable, attachment: Unit) {
cont.resumeWithException(exception)
}
})
}
vs.
withContext(IO) { File(...).writeText(text) }

Is it possible to suspend a coroutine with a timeout?

What I want is a function like this:
suspendCoroutineWithTimeout(timeout: Long, unit: TimeUnit, crossinline block: (Continuation<T>) -> Unit)
That does basically the same thing as the existing suspendCoroutine function but if the callback or whatever was provided in the block dosen't get called within the specified timeout the corutine continues but with a TimeoutException or something like that.
You can combine withTimeout and suspendCancellableCoroutine in a straightforward way for the desired effect:
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long, unit: TimeUnit,
crossinline block: (Continuation<T>) -> Unit
) = withTimeout(timeout, unit) {
suspendCancellableCoroutine(block = block)
}
Perfect answer from #Roman Elizarov.. Just adding my 2 cents on it because I needed a return from that call.. So adding T? return it would be ...
suspend inline fun <T> suspendCoroutineWithTimeout(timeout: Long, crossinline block: (Continuation<T>) -> Unit ) : T? {
var finalValue : T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
If you're using suspendCoroutine, that means you have full control over what you do with the continuation you got. For example, you can pass it to the callback-based async API and, additionally, to a scheduled task that will resume it with exception:
suspend fun mySuspendFun(timeout: Long): String {
val didResume = AtomicBoolean()
fun markResumed() = !didResume.getAndSet(true)
return suspendCoroutine { cont ->
launch(CommonPool) {
delay(timeout)
if (markResumed()) {
cont.resumeWithException(TimeoutException())
}
}
// call Async API, and in the callback, use
// if (markResumed()) {
// cont.resume(result)
// }
}
}
However, Kotlin's standard library supports your use case first-class, as described in Roman Elizarov's answer. I suggest you use that approach in your project.
suspend inline fun <T> suspendCoroutineWithTimeout(
timeout: Long,
crossinline block: (CancellableContinuation<T>) -> Unit
): T? {
var finalValue: T? = null
withTimeoutOrNull(timeout) {
finalValue = suspendCancellableCoroutine(block = block)
}
return finalValue
}
suspend inline fun <T> suspendCoroutineObserverWithTimeout(
timeout: Long,
data: LiveData<T>,
crossinline block: (T) -> Boolean
): T? {
return suspendCoroutineWithTimeout<T>(timeout) { suspend ->
var observers : Observer<T>? = null
val oldData = data.value
observers = Observer<T> { t ->
if (oldData == t) {
KLog.e("参数一样,直接return")
return#Observer
}
KLog.e("参数不一样,刷新一波")
if (block(t) && !suspend.isCancelled) {
suspend.resume(t)
observers?.let { data.removeObserver(it) }
}
}
data.observeForever(observers)
suspend.invokeOnCancellation {
KLog.e("删除observiers")
observers.let { data.removeObserver(it) }
}
}
}
The previous #Roman Elizarov and #febaisi answers have been answered very well, I added a type judgment and livedata on this basis, and I will return only when the conditions are met. Sorry, my English is not very good. –