Created
December 18, 2012 20:20
-
-
Save maxaf/4331584 to your computer and use it in GitHub Desktop.
Tagged types help ease ActorRef confusion.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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! :-)