I am struggling a bit with some fundamentals I thi...
# coroutines
a
I am struggling a bit with some fundamentals I think. I am wondering what is a proper solution for the following: I am creating some sort of Redux architecture, and am now at the part where I am handling effects. These effects should be handled in separate coroutines. Now, the question is as follows. I can implement my
EffectDispatcher
two ways:
Copy code
// as a suspending function
interface EffectDispatcher<E> {
    suspend fun dispatchEffect(effect: E)
}

// return a job
interface EffectDispatcher<E> {
    val scope: CoroutineScope
    fun dispatchEffect(effect: E): Job
}
As far as I understand: In the suspending case (1st) it is the responsibility of the callee to provide a coroutinescope, and in the case of the 2nd the EffectDispatcher itself has a scope in which the effects launch and the callee gets a reference to the dispatched effect job, maybe to cancel it or something, or can cancel every running job with
scope
. Is this correct? As my preference goes I'd go for the 2nd option. EDIT: Option number 3 would be to have
EffectDispatcher<E>
implement
CoroutineScope
. I like that one best actually.
r
Seems like you're making things harder for yourself by not making
dispatchEffect
suspend
This is effectively going from coroutine world back to blocking world
You can still do pretty much anything you'd be able to do in a suspend if you have the Scope
But it'll be harder to read and easier to make mistakes
Also if you want a reference to a Job to manage all dispatched events later you can start them in a `launch`/ `async`/
coroutineScope
block
In the best case options 2 and 3 just mean that all your implementations will look like
Copy code
dispatchEffect(effect: E) = scope.launch {
   //the suspending code you actually wanted to write
}
So all you've done is adding extra boilerplate which should be done by the caller
a
Hmm. But the boilerplate is very limited now:
Copy code
interface EffectDispatcher<E> : CoroutineScope {
    fun dispatchEffect(effect: E)
}

class Store<S, A, E>(
    ....
    override val coroutineContext: CoroutineContext = ....
) : .... , EffectDispatcher<E> {
    ....
    override fun dispatchEffect(effect: E) {
        launch {
            effectHandler.reduceEffect(state, effect, this@Store) // <-- reduceEffect is suspend
        }
    }
}
And the callee:
Copy code
val store = Store(... coroutineContext = ...)
store.dispatchEffect(..)
However, when I make it suspending the API cleans up a bit indeed. So then the question reduces to: Who do I give ownership over the scoping and launching? The callee or the dispatcher?
r
Single-responsibility principle should answer that one for you
👍 1
a
Callee it is
r
Yeah, when I said just calling
launch
was the "best case" I really meant it - if you try to do anything fancy in the dispatcher you could break things really badly really fast
If you really need the extra flexibility you can make a sub-scope in the Dispatcher which will actually give you some stricter concurrency guarantees
a
Thanks, this helped a lot.
👍 1