Coroutine select clause with default


#1

I noticed at https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html it says:

There is no default clause for select expression. Instead, each selectable suspending function has the corresponding non-suspending version that can be used with a regular when expression to select one of the alternatives or to perform default ( else ) action if none of them can be immediately selected.

Is there a decent example of how to use poll in a when clause without doing it above the when statement? I figure there’s not since you can’t define a var on the lhs of the case. So, if you have this in Go:

select {
    case foo := <-someChan1:
        println("Foo: " + foo)
    case bar := <-someChan2:
        println("Bar: " + bar)
    default:
        println("Default")
}

Might look like this in Kotlin?

val foo = someChan1.poll()
val bar = someChan2.poll()
when {
    foo != null -> println("Foo: $foo")
    bar != null -> println("Bar: $bar")
    else -> println("Default")
}

Is there a cleaner way? Maybe this is reasonable?

select {
    someChan1.onReceive { println("Foo: $it") }
    someChan2.onReceive { println("Bar: $it") }
    onTimeout(0) { println("Default") }
}

Thread-safe ReceiveChannel.poll() on channel with nullable elements
#2

I don’t know Go, however in the Kotlin when example you may perform 0, 1 or 2 receive, instead the select example performs 0 or 1 receive, so they are different (and probably the first is wrong).

You can consider another poll version, but you have to verify the someChanX type:

val message = someChan1.poll()?.let{ "Foo: $it" }
           ?: someChan2.poll()?.let{ "Bar: $it" }
           ?: "Default"

println(message)

Looks ok for me.


#3

Not if the channel takes nullables, but that’s the other thread. Otherwise, yes I have to fall back to that because select is not powerful enough. But again, poll has ambiguity problems as mentioned in the other thread. Essentially there needs to be an atomic poll-with-state and there needs to be a onNothingSelected for select (or maybe the docs can suggest onTimeout(0) as what to do in that situation). There are lots of limitations here.

Also, when I don’t need a default clause I still have problems using select due to the need to short-circuit return inside the onXXX clause for several levels higher without reworking code. So essentially I mimic select that looks like this:

run customSelect@ {
    while (true) {
        when {
            chan1.poll()?.also { /* do stuff */ } != null -> return@customSelect
            chan2.poll()?.also { /* do stuff */ } != null -> return@customSelect
        }
        yield()
    }
}

This works for now (my example is a bit more involved). It lets me convert Go logic with or without default clauses w/ the same form of select. Unfortunately I hit other issues with coroutines daily that I either create issues for or star existing ones.