scalaz actors -- A more reasonable approach to a possibly unreasonable programming model.

Why do I hate akka?

If you have talked to me for more than a few minutes about the current state of the world of scala programming, you probably have learned that at some point I started hating akka. Why? There are many reasons, but the one I will name first is the one that many will name first. That a partial function Any => Unit is a horrible type to build a framework around. This, is probably not very controversial, and is an easy target for poking a stick at. I think it is a legitimate complaint. The specific partial function I'm talking about is the receive method of an actor. Here are the relevant snippets from the (akka source)[]:

   * Type alias representing a Receive-expression for Akka Actors.
  type Receive = PartialFunction[Any, Unit]

   * This defines the initial actor behavior, it must return a partial function
   * with the actor logic.
  def receive: Actor.Receive

This is a method you must implement when you create an actor. Any means that it can take any type as input, Unit means you get no return value, it is only called for side-effects, and Partial means that it might throw an exception if send it a message type for which is isn't defined.

Sending a message to an actor is either done with actor.tell(message,sender) or actor ! message:

  def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit
  final def tell(msg: Any, sender: ActorRef): Unit = this.!(msg)(sender)

The tell method that you use to send a message to an actor is a total function from Any => Unit, you can always send, you have no idea if your message was actually processed until runtime. Also, note, this tell method is not a method of an actor itself, you don't send messages directly to actors, you send messages to ActorRefs, which is a reference to an actor that might not exist at all. Maybe it existed at some point, but it might be dead now, but you can still try to send messages to it, and this will always succeed. In this case you might notice a log message about your message going to deadLetters, or you notice that "hmm, things aren't uhh happening".

How did we get here?

So how did we get here, why is akka built around this type? The biggest culprit is a method on akka's actor class, the become method. The become method is a method which lets you change the value receive. This makes an actor act like a state machine, but where some messages don't make sense for some of the states an actor might happen to be in at the time the message is received by the actor. When such a message arrives, the message is ignored. This is not something that with this system, we could expect compile-time verification of, since the compiler can't know what the state of the actor might be in at the time when the message arrives, especially since it could be queued behind other asynchrounouslly delivered messages, any of which might change the state of an actor.

After having worked on several large systems build around akka actors. I just absolutely hate them. The systems have a tendency towards the unmaintainable, because we have given up so much potential for the compiler to do static checking for us. The compiler can't tell us if the message we are sending to an actor would definitely be ignored by the actor. If we think there is a message handler we no longer need in an actor, the compiler won't tell us we were wrong when we try to remove it. Instead your code compiles and at runtime things (perhaps silently) fail, you get to try to debug at runtime why things stop moving.

Here's one possible failure that might show how easily these things can become frustrating:

scala> val s = ActorSystem("stew")
s: = akka://stew

scala> /* lets create an actor which reacts to a "foo" message by side-effecting (natch) */
scala> val a = s.actorOf(Props(new Actor { def receive: Receive = { case "foo" => println("bar") } }))
a: = Actor[akka://stew/user/$a#2102040257]

scala> a ! "foo"

And it works! no problems there. But instead of a "foo" message, lets send a case class instead.:

scala> val s = ActorSystem("stew")
s: = akka://stew

scala> case class Foo() // the message our actor will react to
defined class Foo

scala> val a = s.actorOf(Props(new Actor { def receive: Receive = { case Foo() => println("bar") } }))
a: = Actor[akka://stew/user/$a#2102040257]

scala> a ! Foo
scala> /* nothing happens */

So this all compiles fine, but I don't get my side-effect anymore. Did you spot the error?

Here's the source of the bug:

scala> Foo.isInstanceOf[Foo]
<console>:13: warning: fruitless type test: a value of type Foo.type cannot also be a Foo
res1: Boolean = false

scala> Foo().isInstanceOf[Foo]
res2: Boolean = true

We were sending the Foo companion object to the actor instead of an instance of Foo. But whatever, they are all Any anyway, right?

OK, so lets talk about the problem of "is anyone actually Sending Foo to my actor anymore? can I remove the case Foo() => println("bar") from my actor receive method? How can I know? We can grep our codebase for ! Foo() or tell(Foo())? No. that's not exhaustive enough. Here's another repl session to tell you why.

scala> import

scala> val s = ActorSystem("stew")
s: = akka://stew

scala> trait Bar ; case class Foo() extends Bar
defined trait Bar
defined class Foo

scala> val a = s.actorOf(Props(new Actor { def receive: Receive = { case Foo() => println("bar") } }), "x")
a: = Actor[akka://stew/user/x#214311873]

scala> s.eventStream.subscribe(s.actorFor("/user/x"), classOf[Bar])
res0: Boolean = true

scala> s.eventStream.publish(Foo())

So anyone that can get a reference to our actor can subscribe our actor to receive messages that weren't even sent to us, and how messages are selected is based on a classifier (here classOf[Bar]), which might be hard to discover.

why actors at all?

I think we got here mostly because erlang has some incredible success stories about building resilient systems, using the actor model, in a dynamically typed language. And this is something that isn't deniable. These success stories are well documented. Does this mean that the right way or the only way to build resilient systems is with actor models and throwing away types? I doubt there are many people that would agree to that.

So why is akka so popular? Its really popular, there is no denying that. Although now I would guess that the thing which is asserting the most gravity drawing people towards scala right now is probably spark, last year I would have certainly said akka. Very soon after I started working with scala, I became spellbound by akka. It is certainly a much easier way of thinking about how to protect some mutable state, easier than trying to use the java primitives. You can quickly get something up and running, and it seems kinda fun, messages are being sent around, stuff starts happening... For many of us akka haters, the honeymoon period lasts until you start to try to maintain larger systems involving akka, and trying to track down bugs. Or trying to have some confidence in a refactor, when you don't have the assistance of a type-checker for portions of your code.

Is the actor model good for anything?

Sure. marginally. Actors make for a very easy to think about way of protecting a mutable datum. I can create an actor, have that actor be the only thing that mutates, and just know that my actor will never be processing more than one message at a time, so I don't have to worry about guarding by datum from multi-threaded access. I, however, agree with Paul Chiassano's post about Actor's making a bad concurrency model (you should also read his follow up post).

Do I use them? these days as little as possible. If I do, it wouldn't be with akka, and it wouldn't be a network of actors or a system of actors, but probably a single actor, protecting a single datum, and it would be buried somewhere behind a more sane API. not something I'd expose directly. So if I'm not using akka, what am I use?

scalaz actors

I think the scalaz actors do a fine job at doing the one thing I want from an actor, allow me to easily protect some mutable state without having to think hard about the concurrency story. But, scalaz-actors do away with a lot of the things in akka that I find to be the sources of big trouble. With scalaz actors, we've thrown away the become method. Actors are no longer a Finite State Machine, this allows us to be much more specific about what messages an actor can handle. When you provide a receive method for a scalaz actor, you provide a total function from A => Unit, where A is the ONLY type of message your actor handles, and it should handle any A which is commonly some sealed trait. Now the compiler can check that any message we send to the actor is actually an A, and in our function handling the message, if we do pattern matching, the compiler can do exhaustiveness checking.

Here's a repl session with a scalaz actor:

scala> import scalaz.concurrent._
import scalaz.concurrent._

scala> case class Foo()
defined class Foo

scala> val a = Actor[Foo](f => println("bar"))
a: scalaz.concurrent.Actor[Foo] = Actor(<function1>,<function1>)

scala> a ! Foo()

scala> a ! Foo
<console>:14: error: type mismatch;
 found   : Foo.type
 required: Foo
              a ! Foo

And look, when we try to send the wrong type, the compiler complains. Let's try another:

scala> import scalaz.concurrent._
import scalaz.concurrent._

scala> sealed trait Bar ; case class Foo() extends Bar ; case class Baz() extends Bar
defined trait Bar
defined class Foo
defined class Baz

scala> val a = Actor[Bar]{ case Foo() => println("foo") }
<console>:13: warning: match may not be exhaustive.
It would fail on the following input: Baz()
       val a = Actor[Bar]{ case Foo() => println("foo") }
a: scalaz.concurrent.Actor[Bar] = Actor(<function1>,<function1>)

Yay! compiler warning that we might not handle all the types we claim to. (and you DO have warnings turned into errors in your build, right? You should, you should be using whatever Rob Norris recommends here)

For extra-credit, I highly recommend looking at the source for scalaz's actors. I'm amazed at how little code there is, and figuring out how it actually works is a little mind-bending :) Happy hAkking or whatever.