An actor is kind of object that interacts with other actors and with the external world through messages. An actor object can have a private internal mutable state that can be modified and accessed externally through messages, but not directly. Actors are growing in popularity in recent years due to their consistent programming model, and have been tested successfully in multi-million user applications, such as WhatsApp that is built with Erlang, the language that brings actors into the limelight:
import kotlinx.coroutines.experimental.channels.actor
sealed class CounterMsg
object IncCounter : CounterMsg()
class GetCounter(val response: CompletableDeferred<Int>) : CounterMsg()
fun counterActor(start:Int) = actor<CounterMsg> {
var counter = start
for (msg in channel) {
when (msg) {
is IncCounter -> counter++
is GetCounter -> msg.response.complete(counter)
}
}
}
To write an actor, first, we need to define which messages we want to send. Here, we are creating two messages, IncCounter and GetCounter. GetCounter has a CompletableDeferred<Int> value that will let us know the counter value outside the actor.
We can use the actor<CounterMsg> builder to create actor. Inside our actor coroutine, we have access to the channel property, ReceiveChannel<CounterMsg>, to receive the messages and react to them. The counterActor(Int) function will return SendChannel<CounterMsg>; therefore, the only functions that we can call are send(CounterMsg) and close():
fun main(args: Array<String>) = runBlocking {
val counterActor = counterActor(0)
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
counterActor.send(IncCounter)
}
}
val counter = CompletableDeferred<Int>()
counterActor.send(GetCounter(counter))
println("counter = ${counter.await()}")
println("time = $time")
}
Actors can be hard to grasp at the beginning but once, you understand, the actor model is straightforward for creating complex and powerful systems.