what am I supposed to use instead of actor if I ne...
# coroutines
f
what am I supposed to use instead of actor if I need an actor?
c
The deprecated
actor { }
function is little more than a
Channel
, so that could be the basis for your own implementation of an Actor pattern
f
why would I prefer to implement my own over a library implementation? The chances that my own implementation will be superior to even a deprecated one is small.
sure, it’s simple to get a simple one that works but I don’t know all the edge cases
c
that’s basically why it’s still there. There is no official replacement within the core coroutines library, but the current actor API really doesn’t offer much outside of a standard Channel and some basic guidance for how to use actors, and the
actor
API itself wasn’t really designed to handle all the use-cases of Actors properly, either. There’s been an open issue to create a robust actor framework for many years, but it’s basically on hold indefinitely. So some folks do choose to keep using the
actor
function because it’s been said that it’s not going to get removed until/if a proper replacement does come. But at the same time, since there’s so little to those APIs, its’ easy enough that you may want to create your own set of APIs based around Channels to future-proof yourself in the case that it does get removed eventually.
e
setting up a channel + launching a ReceiveChannel.consume is pretty much the same as what
actor()
used to give you, there wasn't anything magic there
c
Or alternatively, actors are just one mechanism for managing concurrent work. Depending on your needs, other patterns like MVI be a suitable alternative. Actors are primarily concerned with moving data asynchronously through a series of stages, but you may be more interested in the aspect of queueing to ensure atomic updates to some shared variable. MVI can be a great pattern in this case, and there are several libraries available to help with MVI state management. Ballast is a library I maintain, but there are others with different DSLs that you may find more to your liking
And just to demonstrate how little there is to the
actor
function, this is pretty much all you’d need for a full replacement of that function. Again, there’s nothing magic here
Copy code
public fun <T> CoroutineScope.actor(block: suspend (ReceiveChannel<T>)->Unit): SendChannel<T> {
    val channel = Channel<T>(Channel.RENDEZVOUS)
    launch {
        block(channel)
    }
    return channel
}
e
that doesn't handle cancellation the way that
actor()
or
consume()
do, but conceptually it's there
this would be closer to how the deprecated
actor()
behaves (since
consume()
also cancels the channel when the block completes or is cancelled)
Copy code
fun <E> CoroutineScope.actor(
    context: CoroutineContext = EmptyCoroutineContext,
    capacity: Int = 0,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    onCompletion: CompletionHandler? = null,
    block: suspend ReceiveChannel<E>.() -> Unit,
): SendChannel<E> {
    val channel = Channel<E>(capacity)
    val job = launch(context, start) {
        channel.consume { block() }
    }
    if (onCompletion != null) job.invokeOnCompletion(onCompletion)
    return channel
}
although it doesn't handle
start = CoroutineStart.LAZY
and the original implementation does
still, no magic