3

I am playing around with Scala right now and tried to figure out some best practices about how to design classes. (Trying Scala since a week or so.)

Since my Erlang time I am a huge fan of message passing and actor based software. In most Scala examples actor classes are implemented like this:

object Foo
object Bar
class MyActor extends Actor {
  def receive = {
    case Foo => ...
    case Bar => ...
    case _ => ...
  }
}

But what I learned from my object oriented (interfaces and polymorphism) carrier tells me that this concept is not very flexible.

MyActor could be replaced by MyAdvancedActor but there is no contract which defines which messages an MyActor implementation needs to implement.

When I think about writing Actors in Scala I tend to write a trait which specifies some methods. The MyActor implementation needs to implement this methods in which the can send its own private messages to itself. With this approach we have a specified interface and can replace the MyActor implementation in a type-safe manner.

In my time of reading scala tutorials and examples I did not come across such an class design. Is it not common sense or are there better ways in doing this in Scala? Or are these tutorials just to small to cover such a topic?

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
r2p2
  • 384
  • 4
  • 14

2 Answers2

5

Common practice is to use an algebraic data type in such cases: you could create a sealed base type for all messages like this:

sealed trait MyActorMessages
object Foo extends MyActorMessages
object Bar extends MyActorMessages

But this kind of contract is not enforced by compiler. You could use use Typed Channels to enforce this contract:

class MyActor extends Actor with Channels[TNil, (MyActorMessages, MyActorReply) :+: TNil] {
  channel[MyActorMessages] { (req, snd) ⇒
    req match {
      case Foo  ⇒ ...
      case Bar ⇒ ... // You'll get a warning if you forget about `Bar`
    }
  }
}

Compiler will force you (with warning) to process all possible message types (in this case all subtypes of MyActorMessages), and senders will be forced to send only valid messages using <-!- method (with compilation error).

Note that senders can also use unsafe method ! to send invalid messages.

senia
  • 37,745
  • 4
  • 88
  • 129
  • Channels are a new topic for me but I think that I understand what they do. Is type safety not so important nowadays? I thought it was a useful feature of statically typed languages. So that errors can be detected before the binary is shipped. – r2p2 Aug 08 '13 at 20:27
  • @r2p2: type safety is important, that's why such things as Typed Channels and [Typed Actors](http://doc.akka.io/docs/akka/2.2.0/scala/typed-actors.html) are exists, but it's hard to use actors only in type safe way. There are such things as `become` and you should have the way to reply to any sender and so on. There are plenty of discussions on this topic, for instance: [Why are messages to akka actors untyped?](http://stackoverflow.com/q/5547947/406435). – senia Aug 08 '13 at 20:40
2

I really like the solution from @senia. It's an effective use of Akka's new Typed Channels feature. But if that solution does not suit you, I can offer something a bit more traditional to the OO world. In this solution you specify the actual message handling behavior for the actor via a strategy implementation that the actor is constructed with. The code would look something like this:

//Strategy definition
trait FooStrategy{
  def foo1(s:String):String
  def foo2(i:Int):Int
}

//Regular impl
class RegularFoo extends FooStrategy{
  def foo1(s:String) = ...
  def foo2(i:Int) = ...
}

//Other impl
class AdvancedFoo extends FooStrategy{
  def foo1(s:String) = ...
  def foo2(i:Int) = ...
}

//Message classes for the actor
case class Foo1(s:String)
case class Foo2(i:Int)

//Actor class taking the strategy in the constructor
class FooActor(strategy:FooStrategy) extends Actor{      
  def receive = {
    case Foo1(s) => sender ! strategy.foo1(s)        
    case Foo2(i) => sender ! strategy.foo2(i)
  }
}

Then to create instances of this actor:

val regFooRef = system.actorOf(Props(classOf[FooActor], new RegularFoo))
val advFooRef = system.actorOf(Props(classOf[FooActor], new AdvancedFoo))

One benefit here is that you are decoupling the business logic of the actor from it's normal message handling behavior. You are letting the actor class just do actor stuff (receive from mailbox, reply to sender, etc...) and then the real business logic is encapsulated in a trait. This also makes it much easier to test the business logic in isolation with unit tests for the trait impls. If you needed actor type stuff in the trait impls, then you could always specify an implicit ActorContext to the methods on FooStrategy, but then you would lose the complete decoupling of actor and business logic.

Like I said earlier, I like the solution from @senia; I just wanted to give you another option that might be more traditional OO.

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
  • Nice trick. I never thought about changing the behavior while the interface stays the same. Also a cool way to implement state machines. – r2p2 Aug 08 '13 at 20:33