Skip to content

Instantly share code, notes, and snippets.

@maxaf
Created December 18, 2012 20:20
Show Gist options
  • Save maxaf/4331584 to your computer and use it in GitHub Desktop.
Save maxaf/4331584 to your computer and use it in GitHub Desktop.
Tagged types help ease ActorRef confusion.
// Mock Akka in the absence of artifacts. Don't pay attention to this
// part. Oh, and I'm stuck in Akka 1.3.x land, so some of this may
// look "retro" to you.
trait ActorRef {
def !(msg: Any) {}
}
trait Actor {
def self: ActorRef = new ActorRef {}
type Receive = PartialFunction[Any, Unit]
def receive: Receive
}
def actorOf(factory: => Actor): ActorRef = factory.self
// Trait to be mixed into Actor's companion object. Defines tag and
// handy type aliases.
trait TaggedActorRef[A <: Actor] {
trait Tag
type Ref = ActorRef with Tag
// You call this apply method in order to invoke actorOf() on an
// actor factory & "wrap" the resulting ActorRef in a tagged
// ref. There's zero overhead involved here because all we do is
// cast the ActorRef to the (ultimately erased) tagged type.
def apply(factory: => Actor): Ref = actorOf(factory).asInstanceOf[Ref]
}
// Set up an actor.
class FirstActor extends Actor {
def receive = { case _ => }
}
// One line of boilerplate is required per actor type in order to set
// up tagged actor ref. This can be a companion object or whatever.
object FirstActor extends TaggedActorRef[FirstActor]
// Bear with me. Some more setup now, just to make things
// interesting...
class SecondActor extends Actor {
def receive = { case _ => }
}
object SecondActor extends TaggedActorRef[SecondActor]
// Set up some refs.
val first = FirstActor(new FirstActor)
val second = SecondActor(new SecondActor)
// Here's a method that takes *only* an ActorRef pointing to a
// FirstActor, and not any other actor.
def sendToFirstActor(ref: FirstActor.Ref) { /* ... whatever */ }
sendToFirstActor(first) // compiles
sendToFirstActor(second) // doesn't compile
/*
* error: type mismatch;
* found : second.type (with underlying type SecondActor.Ref)
* required: FirstActor.Ref
* sendToFirstActor(second) // doesn't compile
* ^
*/
// We can have nice things now! Here's an actor that can forward its
// message to either one of the two destinations, defined at
// instantiation time:
class Router(next: Either[FirstActor.Ref, SecondActor.Ref]) extends Actor {
def receive = {
case msg => next.fold[ActorRef](identity, identity) ! msg
}
}
// Setting up this actor is a breeze, and there's no confusion about
// which actor ref goes where:
val router1 = actorOf(new Router(next = Right(second))) // compiles
val router2 = actorOf(new Router(next = Left(second))) // doesn't compile
/*
* error: type mismatch;
* found : second.type (with underlying type SecondActor.Ref)
* required: FirstActor.Ref
* val router2 = actorOf(new Router(next = Left(second))) // doesn't compile
* ^
*/
// I'm sure that this pattern has many other applications. For now,
// we're using it to ensure compile time checking of actor chains
// similar to the Router example above.
@viktorklang
Copy link

Ah! So you're tagging the implementation and not the types of messages it can receive! Cool, 1.3 indeed is a bit retro but rock on! :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment