5 Myths about Actors

Our latest guest on [event-driven] is Akka Tech Lead, Dr. Roland Kuhn.

(This interview was broken into two parts due to time constraints. In the first episode I asked Roland to dispel some of the myths surrounding actors, while in the forthcoming episode we are going to chat about Java8, Akka 2.3, RxJava, Reactor and more.)

1. OK, let's dive right into it. One of the most widespread claims people make about actors is that actors are coarse abstractions. What do you have to say for that?

Actors are a general computational model, they can be used to perform any calculation you might want to do. As such they can be used at different levels of abstraction or organization in your code: you could start by writing high-level services (as in SOA) where each is represented by one actor and contains all the processing logic which that service provides; that would be very coarse indeed. You could then break down the internal function of that service like you would break down a large corporation into divisions, and teams and so on.

Let me wrap that up by saying that the unit you model with an actor can be as coarse or as fine as you want to make it but you seem to be heading towards a different kind of granularity.

Noel Welsh's conclusion on the same subject was that "Concurrent programming involves at least three distinct concerns: concurrency, mutual exclusion, and synchronisation. With actors the first two always come combined, and you’re left to hand-roll your own synchronisation through custom message protocols. It’s an unhappy state of affairs if you want to do something as simple as separating control of concurrency and mutual exclusion."

He was arguing that actors come as a full package, with synchronization and communication wrapped up. The power of the actor model is that it reifies asynchronous message passing, working with independent agents which are completely encapsulated and shielded from each other. This implies how concurrency, mutual exclusion and synchronization must work: everything is inherently concurrent, there is no global synchronization provided by the model and the only exclusion is that actors process their messages sequentially. This package cannot be untied without losing the benefits of the system.

2. Rich Hickey points out another issue which is actors couple the producer with the consumers

It is unfortunate that we cannot ask him how he means that, since the opposite is true.

Let us start from local method calls: the calling context is suspended and cannot proceed until the method call returns. This is the strongest form of coupling in that the caller will need to wait for the result, it will need to make sure that the callee is actually available for processing the request and it will need to handle exceptions raised while the callee processes the message. If we now turn to synchronous message passing we can make the processing of the request happen asynchronously, i.e. after the request has been communicated both objects can continue to execute their logic. But this means that both objects need to be ready for communication at the same point in time, which also is a form of coupling: the caller needs to wait until the callee is ready.

The only way to completely decouple caller and callee is by making the message-passing asynchronous. With this approach the caller is neither blocked nor otherwise affected by the processing of the message in the callee. And this is exactly the foundation of the Actor Model. It might be of interest that this is also the most prominent difference to CSP or the π calculus.

...I suppose he may have referred to actor references, that is, when say in an actor (A) you receive a message and then send a message to another actor (B), then the reference to B would be in the receive method of A...

We can only speculate…

Right. Let's move onto the next claim.

3. Actors do not compose

My favorite! Taken at face value (looking at the API) actors are functions which do not return anything: in Akka both tell() and Receive return Unit. This is why the statement arises that they cannot be composed, and what is meant is that they do not compose like normal functions

Yes, indeed — I believe this is the root of this claim.

But actors do compose in the same sense our human society is composed.

What do you mean by that?

A team of people work together to achieve a given task, and it is usually the case that there is a leader which is asked to do it, then he or she divides up the work, and once everything is done the task’s completion is reported back to the one who had requested it. Actor systems work in exactly the same way.

So I guess what you are saying is that actors are indeed composable but on a different level?

Yes. You could say that actors compose on a protocol level, transforming incoming requests and replies, splitting up work and aggregating results. Or even translating. I made an interesting discovery when developing Typed Channels — which we’ll talk about next I guess.

...do share. What was the discovery?

If you restrict actors to an external request-reply protocol then they behave like functions, with input and output. And those can then be composed like normal functions, using the ask-pattern in Akka for example.

Do you have an example for this?

Well, `someActor ? MyQuery` will return a `Future[MyResponse]`, which can be mapped, flatMapped etc. as usual, including passing the value to another actor via the ask operator. Of course getting the right return type requires Typed Channels. And that was the answer to another criticism that actors are not usefully typed.

4. Actors are not usefully typed

It is true that the ability to send just about anything to an actor without help from the compiler makes it more difficult to refactor actor programs than—say—a normal Scala program without actors. When Scala received support for macros I set out to explore possible remedies to this problem, and the task is a bit more difficult than you might initially assume.

Totally! When macros came out in 2.10, this was the first thing that occurred to me as a potential use case (i.e. whether some extra typesafety could be achieved for actors)

The type of an actor should first contain all the permissible message types, that is all inputs that it might handle. Here it is not sufficient to use a single type parameter because you might want to handle an actor which handles some control commands and passes everything else through to another actor. This means a set of types, or a type union, something which is not directly expressible in Scala’s type system, hence the need for macros. In order to allow an actor to reply, we implicitly capture the sending actor’s reference per message. So not much would be gained without making the replies also type-safe. Therefore, we need to express not only a set of types but a multi-map: for every input message the actor might reply with a set of output types.

The idea is then to type-check at the place in the code where the message is sent that

  • the actor has declared a suitable input type
  • the captured sender will be able to process a possible reply
  • and replies going back and forth in ping-pong fashion also need to be verified This is the type calculation that I could not figure out without the help of macros.

All this is implemented and an experimental part of Akka 2.2, so you can play around with it.

...getting a compilation error when sending an invalid message to an actor would be sweet!

Yes, exactly. And I worked hard to make the compiler error message meaningful.

Sounds very exciting for sure!

The problem currently is that Scala’s runtime reflection support —- which is needed for certain operations —- is not thread-safe, which means that in practice you cannot really work with this module just yet. But Jason Zaugg is working on fixing that for Scala 2.11, as far as I know.

Anyway, it looks promising and I think it will be helpful. But it remains to be seen how safe we can actually make it. Tracking the sending of messages seems feasible, but checking that an actor’s implementation conforms to its declared protocol might be very much harder.

5. Going back to the claims, the last one is actors don't encourage Functional Programming

yes, Actors vs. FP is also a hot topic. The twitter discussion inspired me to look at Cloud Haskell, which is a 1:1 port of Erlang actors into the prototypic FP language. Reading the code I came to the conclusion that there is no difference in practice between writing Akka actors without vars and Erlang actors and Haskell actors. Hence I must conclude that at least Akka actors are as FP-friendly as actors can be.

I believe this claim has two sides: one part of it is the composability claim we already discussed and the other side is mutability

Actors are fundamentally about modeling mutable state in a distributed system, in a safe fashion. But you can write an actor without explicitly having mutable state: the Actor Model demands that in response to a message the behavior is computed which shall be applied to the next message. And this behavior is what defines the actor.

You mean a strategy where one might start out with context.become, then switch over to using var-s if better performance is needed?

Yes, exactly: context.become allocates a new object, which can become a problem if you do it many million times per second. We prefer the clarity of this approach in all cases where the allocation overhead does not demand the use of a variable field.