20

Consider these two traits:

trait Poked extends Actor {
  override def receive = {
    case Poke(port, x) => ReceivePoke(port, x)
  }

  def ReceivePoke(port: String, x: Any)
}

trait Peeked extends Actor {
  override def receive = {
    case Peek(port) => ReceivePeek(port)
  }

  def ReceivePeek(port: String)
}

Now consider I can create a new Actor that implements both traits:

val peekedpoked = actorRef(new Actor extends Poked with Peeked)

How do I compose the receive handlers? i.e., the receiver should be something like the following code, though "automatically generated" (i.e., all traits should compose):

def receive = (Poked.receive: Receive) orElse (Peeked.receive: Receive) orElse ...
Seth Tisue
  • 29,985
  • 11
  • 82
  • 149
Hugo Sereno Ferreira
  • 8,600
  • 7
  • 46
  • 92

2 Answers2

28

You can use super[T] to reference members of particular super classes/traits.

For example:

trait IntActor extends Actor {
    def receive = {
        case i: Int => println("Int!")
    }
}

trait StringActor extends Actor {
    def receive = {
        case s: String => println("String!")
    }
}

class IntOrString extends Actor with IntActor with StringActor {
    override def receive = super[IntActor].receive orElse super[StringActor].receive
}

val a = actorOf[IntOrString].start
a ! 5 //prints Int!
a ! "Hello" //prints String!

Edit:

In response to Hugo's comment, here's a solution that allows you to compose the mixins without having to manually wire their receives together. Essentially it involves a base trait with a mutable List[Receive], and each mixed-in trait calls a method to add its own receive to the list.

trait ComposableActor extends Actor {
  private var receives: List[Receive] = List()
  protected def registerReceive(receive: Receive) {
    receives = receive :: receives
  }

  def receive = receives reduce {_ orElse _}
}

trait IntActor extends ComposableActor {
  registerReceive {
    case i: Int => println("Int!")
  }
}

trait StringActor extends ComposableActor {
  registerReceive {
    case s: String => println("String!")
  }
}

val a = actorOf(new ComposableActor with IntActor with StringActor).start
a ! 5 //prints Int!
a ! "test" //prints String!

The only thing to keep in mind is that the order of the receives should not be important, since you won't be able to easily predict which one is first in the chain, though you could solve that by using a mutable hashmap instead of a list.

Dan Simon
  • 12,891
  • 3
  • 49
  • 55
  • That's very interesting, thank you :-) But it assumes the pre-existence of a type IntOrString that is both an Int and a String, and that IntOrString knows it should compose those (which, if I'm building a framework, the other may overlook). Isn't it possible to make the IntActor and StringActor traits automatically compose? – Hugo Sereno Ferreira Dec 30 '11 at 20:19
  • 3
    The order is given by linearization of the mixed-in traits, hence “predictable” ;-) And using prepending matches the overriding of later traits wrt. earlier ones, so I think your solution is very nice! – Roland Kuhn Dec 31 '11 at 11:53
  • Excellent display of your scala-fu! :-) – Hugo Sereno Ferreira Dec 31 '11 at 12:32
5

You can use empty Receive in base actor class and chain receives in their definitions. Sample for Akka 2.0-M2:

import akka.actor.Actor
import akka.actor.Props
import akka.event.Logging
import akka.actor.ActorSystem

class Logger extends Actor {
  val log = Logging(context.system, this)

  override def receive = new Receive {
    def apply(any: Any) = {}
    def isDefinedAt(any: Any) = false
  }
}

trait Errors extends Logger {
  override def receive = super.receive orElse {
    case "error" => log.info("received error")
  }
}

trait Warns extends Logger {
  override def receive = super.receive orElse {
    case "warn" => log.info("received warn")
  }
}

object Main extends App {
  val system = ActorSystem("mysystem")
  val actor = system.actorOf(Props(new Logger with Errors with Warns), name = "logger")
  actor ! "error"
  actor ! "warn"
}
Andrey Kuznetsov
  • 11,640
  • 9
  • 47
  • 70