Suspend function reference as parameter of let gets error - kotlin

Why Kotlin can't reference to suspend function as the parameter of let/also and other functions?
class X
fun a(x: X) {}
suspend fun b(x: X) {}
X().let(::a)
X().let(::b) // Error: Type mismatch

You can only call suspend functions from a coroutine or another suspend function.
And let does not take a suspend function as a parameter.
public inline fun <T, R> T.let(block: (T) -> R): R
So as with any other type, the function declaration has to match. Passing a suspend function to another function that does not accept a suspend function will not work.
It would work when you have a function like:
This is just an example, no real usecase for a suspend function for printing a log!
suspend inline fun log(block: suspend () -> String) {
val message: String = block() // we assume block takes some time to be computed
return println(message) // once its available, we print it
}
You can use the log function like:
suspend fun complexError(): String {
// takes time to compute...
return "message"
}
// usage
suspend fun errorHandling() {
log(::complexError) // pass a reference to complexError()
// or
log() { complexError() }
}

Related

Can we have an extension function for suspend function?

if i use :
suspend fun <T> A(block: suspend () -> T){
///std.
}
all things go right but :
suspend fun <T> (suspend () -> T).A(){
///std.
}.
no compilation errors , but i cant use it with suspend functions.
for example that we have this fun (do work is a suspend function):
accountManager.doWork(password)
in case #1 its works fine:
A {
accountManager.doWork(password)
}
in case #2 it does not work as expected(compilation error):
accountManager.doWork(password).A()
The receiver of
accountManager.doWork(password).A()
is whatever doWork(password) returns, not the doWork function itself. Let's suppose that doWork returns String, then the above would have worked if A were an extension function on String:
// Depending on what you do in its implementation,
// A does not need to be a suspending function
fun String.A() {
}
If you want A's receiver to be the function doWork instead, the syntax is:
(accountManager::doWork).A()
Then the above will compile if A is declared like this:
suspend fun <T, R> (suspend (T) -> R).A() {
}
Notice that the receiver type is a function that takes one parameter, since doWork takes one parameter.
If what you actually want to do is to use the entire suspending lambda you passed to A as a parameter here...
A {
accountManager.doWork(password)
}
...as the receiver of the new extension function A that you are declaring, then your attempt is correct:
suspend fun <T> (suspend () -> T).A(){
}
You should call it on a suspending lambda:
suspend { accountManager.doWork(password) }.A()
Though I'm not sure why you would prefer this to the way more readable A { ... } syntax.

Kotlin coroutine suspend and delay

I want to simulate file loading and I want to delay code for 4 seconds and I can't do this.
suspend fun showLoadingProgress() : String = suspendCancellableCoroutine{ continuation ->
while (fileIsBeingLoaded())
{
delay(4000)
val percent = ((loadedBites.toDouble() / fileBites.toDouble())*100).toInt()
continuation.resume("$loadedBites/$fileBites ($percent%)")
}
}
I have error that: suspension functions can be called only from coroutine body. BUT
When I have code like this, without returning String, then my delay works.. WHY?:
suspend fun showLoadingProgress() {
while (fileIsBeingLoaded())
{
delay(4000)
val percent = ((loadedBites.toDouble() / fileBites.toDouble())*100).toInt()
continuation.resume("$loadedBites/$fileBites ($percent%)")
}
}
How can I make delay and return a String?
suspendCancellableCoroutine is mainly used with callbacks to suspend a coroutine execution until the callback fires, for example:
suspend fun getUser(id: String): User = suspendCancellableCoroutine { continuation ->
Api.getUser(id) { user ->
continuation.resume(user)
}
continuation.invokeOnCancellation {
// clear some resources, cancel tasks, close streams etc.
}
}
delay doesn't work in suspendCancellableCoroutine block because it is not marked as suspend and therefore we can't call suspend function in it. suspendCancellableCoroutine function is defined like:
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T = ...
If it was defined something like this (please note block marked as suspend):
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: suspend (CancellableContinuation<T>) -> Unit
): T = ...
then we would be able to call delay function in it.
I don't know why you use while loop, it seems it is redundant there. Or you use it incorrectly for the loading progress.
You don't have callbacks, so you can get rid of suspendCancellableCoroutine:
suspend fun getLoadingProgress(): String {
delay(4000)
val percent = ((loadedBites.toDouble() / fileBites.toDouble())*100).toInt()
return "$loadedBites/$fileBites ($percent%)"
}
suspend fun showLoadingProgress() {
while (fileIsBeingLoaded()) {
val progress = getLoadingProgress()
// use progress
}
}
Another approach is to use Flow to emit the loading progress. It will look something like the following using flow builder:
fun getLoadingProgress(): Flow<String> = flow {
while (fileIsBeingLoaded()) {
delay(4000)
val percent = ((loadedBites.toDouble() / fileBites.toDouble())*100).toInt()
emit("$loadedBites/$fileBites ($percent%)")
}
}
And collect values:
someCoroutineScope.launch {
getLoadingProgress().collect { progress ->
// use progress
}
}

A 'return' expression required in a function with a block body despite inlined lambda which has the return

I'm trying to create an inlined try-catch helper function but running into a compilation error due to the return statement occurring within the inlined lambda. Below is some code demonstrating the same issue
fun isStringEmpty(myString: String): Boolean {
stringOpHelper {
return myString.length == 0
}
}
inline fun <T> stringOpHelper(fn: () -> T) {
println("performing string operation")
fn()
}
This will compile with the desired effect if a return is added after the inlined function call or an exception is thrown, but that code should be unreachable. Eg:
fun isStringEmpty(myString: String): Boolean {
stringOpHelper {
return myString.length == 0
}
TODO("this is actually unreachable")
}
inline fun <T> stringOpHelper(fn: () -> T) {
println("performing string operation")
fn()
}
My expectation was that the compiler would see that stringOpHelper always calls fn() and the fn in isStringEmpty always returns, so the inlined stringOpHelper call always returns.
Is it possible to define the inline helper function in a way that avoids the need for the unreachable exception / return in the calling function? Otherwise, what's the reason for why this isn't possible?
There is mechanism for such purposes called contracts, but this feature is experimental and its usage must be marked with #ExperimentalContracts or #OptIn(ExperimentalContracts::class)
#OptIn(ExperimentalContracts::class)
inline fun <T> stringOpHelper(fn: () -> T) {
contract {
callsInPlace(fn, kotlin.contracts.InvocationKind.EXACTLY_ONCE)
}
println("performing string operation")
fn()
}

Passing coroutine function as function parameter

I need to pass a coroutine function as a parameter for another function. For example:
private fun handleIt(here: Long, call: (hereId: Long) -> Unit) {
call(here)
}
and then from the coroutine scope:
GlobalScope.launch {
handleIt(3) { viewModel.doThings(hereId) }
}
The viewModel function looks like this:
suspend fun doThings(hereId: Long) {
withContext(coroutineContextProvider.io) {
doSomething(hereId)
}
}
But now, I got the error: "Suspension functions can be called only within coroutine body. Any suggestions?
Simple solution would be to mark both the block and handleIt function as suspending:
suspend fun handleIt(here: Long, call: suspend (hereId: Long) -> Unit) {
call(here)
}

Kotlin - inline functions with multiple parameters

I can define an inline function in Kotlin with:
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
But how do I call this function?
For a normal inline function with only one parameter, I would use:
func {
doSomething()
}
Is there a similar syntax for functions with more than one inline parameter?
Only the last function parameter is passed outside the call operator.
For example:
fun foo(a: () -> Unit, b: () -> Unit)
foo({ println("hello") }) {
println("world")
}
The other function parameters are just passed in the normal argument list inside the parenthesis, the last can optionally be moved outside of those parenthesis as is done with most calls.
There are several ways to achieve this.
Probably the nicest is to use a bound function reference for the parameters before the last one:
fun foo(){
func(::foo){foo()}
//this also works:
func(::foo, ::foo)
//or place the other call within parentheses in a lambda. (you can only put the last lambda behind the method call.
func( { foo() } ){foo()}
}
inline fun func(a: () -> Unit, b: () -> Unit){
a()
b()
}
If you want to call an objects method just place that objects name in front of the ::
class Bar {
val baz = Baz()
fun foo() {
func(baz::func2, ::foo)
}
fun func(a: () -> Unit, b: () -> Unit) {
a()
b()
}
}
class Baz{
fun func2(){}
}