Kotlin Unit and coroutine body - kotlin

Assume there are some chained suspend functions like this.
suspend fun getData(): Boolean {
return request().also {
delay(1000)
}
}
suspend fun request(): Boolean {
return call()
}
suspend fun call(): Boolean {
return run {
delay(1000)
true
}
}
The above works allright. But if we convert the also block into a Unit param we will get an error: Suspension functions can be called only within coroutine body
suspend fun getData(): Boolean {
return request {
delay(1000) //the error
}
}
suspend fun request(action: (Boolean) -> Unit): Boolean {
return call().also(action)
}
Why is it so and is it possible to make the Unit inherit the coroutine body?

You should make the lambda parameter suspend:
suspend fun request(action: suspend (Boolean) -> Unit): Boolean {
Now the lambda parameter has a suspending function type, but also doesn't take that, so you can't pass it in directly like also(action), you need to change it to:
return call().also { action(it) }
See also: how to pass suspend function as parameter to another function? Kotlin Coroutines

Related

How to return a result of high-order function from inner function

I have a "high-order" function that have to return some value. Inside that "high-order" function there is an "inside" function which really is a producer of the return value of a "high-order" function.
It is simpler to show it with an example what I mean:
lifecycle.coroutineScope.launch {
val result = doWork()
Log.d("Tag", "some result: ${result.someString}")
}
private val service = SomeService()
suspend fun doWork(): DoWorkResult {
fun onSomeString(someString: String): DoWorkResult {
//some execution
val returnResultForDoWork = DoWorkResult(someString)
//How to return 'returnResultForDoWork' from fun doWork
return returnResultForDoWork
}
service.getString { someString ->
onSomeString(someString)
}
}
class SomeService() {
suspend fun getString(
onResult: (String) -> Unit
) {
delay(1000)
onResult("work is done")
}
}
data class DoWorkResult(val someString: String)
flow execution:
call service.getString
call onSomeString(someString) when a someString is return from service.getString
in onSomeString analyse/handle a someString and return (how?) a DoWorkResult(someString) from doWork
My question is how to return a result of an onSomeString function as a result of a doWork function?
Suspend functions don't need higher order callbacks like that. Really, it's an antipattern, because it restores back "callback hell" that coroutines solve. A proper version of your function would look like:
class SomeService() {
suspend fun getString(): String {
delay(1000)
return "work is done"
}
}
And then your calling function becomes:
suspend fun doWork(): DoWorkResult {
val serviceReturnValue = getString()
//some execution
val returnResultForDoWork = DoWorkResult(serviceReturnValue)
return returnResultForDoWork
}
But let's suppose your service function is not a suspend function, but rather it is asynchronous with a callback, and you don't have control over the source code to make it a suspend function instead.
class SomeService() {
fun getString(
onResult: (String) -> Unit
) {
val handler = Handler(Looper.myLooper())
thread {
Thread.sleep(1000) //simulate some work
handler.post { onResult("work is done") }
}
}
}
Then to be able to return the callback's inner value in a suspend function, you need to convert the asynchronous function into a suspending one. This can be done with suspendCoroutine or suspendCancellableCoroutine. There are many examples you can look up on this site or online, but here's a quick sample. You can write it as an extension function to work like an overloaded version of the asynchronous function.
suspend fun SomeService.getString(): String = suspendCoroutine { continuation ->
getString { continuation.resume(it) }
}
Now you can call this proper suspending version of the function just as in my second code block above.
Honestly, I am not quite sure if I really understand what you try to do but...
is this what you looking for?
private val service = SomeService()
data class DoWorkResult(val someString: String)
suspend fun doWork(): DoWorkResult {
fun onSomeString(someString: String): DoWorkResult {
//some execution
val returnResultForDoWork = DoWorkResult(someString)
//How to return 'returnResultForDoWork' from fun doWork
return returnResultForDoWork
}
return service.getString { someString ->
onSomeString(someString)
}
}
class SomeService {
suspend fun getString(onResult: (String) -> DoWorkResult): DoWorkResult {
delay(1000)
val myStringFromNetworkOrSomething = "work is done"
return onResult(myStringFromNetworkOrSomething)
}
}

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)
}
}
}

Write extension function on Flow<T>.catch

I would like to modify the default Flow.catch function and always call a specific suspend function logOutIfUserHasNoAccount(). My current implementation is:
private suspend fun <T> Flow<T>.catchLoginError(action: suspend FlowCollector<T>.(Throwable) -> Unit): Flow<T> {
logOutIfUserHasNoAccount()
return catch(action)
}
My questions is: Is there any disadvantage that my Flow.catchLoginError function is also a suspend function? The default implementation of catch is:
// Not suspending
public fun <T> Flow<T>.catch(action: suspend FlowCollector<T>.(Throwable) -> Unit): Flow<T> { /* compiled code */ }
Using
suspend fun login() = flow {
}.catchLoginError( e ->
// here it should call "logOufIfUserHasNoAccount" first
doStuff(e)
}
Your function when called immediately calls the logout function, and then returns a wrapped version of the original Flow that will catch errors when it is collected.
To logout only when errors occur, you should move the call inside the catch call's block. It doesn't need to be a suspend function either.
fun <T> Flow<T>.catchLoginError(action: suspend FlowCollector<T>.(Throwable) -> Unit): Flow<T> = catch { error ->
logOutIfUserHasNoAccount()
action(error)
}

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.

Adding a yield to a non-suspending function

I'm trying to build an interruptible version of CharSequence. Something along these lines:
class InterruptableCharSequence(private val delegate: CharSequence) : CharSequence {
override fun get(index: Int): Char = runBlocking {
yield() // Suspend here, in case we have cancelled
delegate[index]
}
override fun subSequence(start: Int, end: Int): CharSequence {
return delegate.subSequence(start, end)
}
override fun toString(): String {
return delegate.toString()
}
}
Since this is implementing an existing interface I can't make get a suspending function, but I'd like to call it from a co-routine (several layers up) and be able to interrupt/cancel it. How would I go about making this possible? Is there any way to do so without blocking like this or creating a new coroutine for each Char?