Akka.NET provides an F# API that makes it trivial to define Akka actors as F# functions over Akka mailboxes. And as long all messages handled by actor can be described using a single discriminated union, the actor mailbox is strong typed. The problem is that putting all message definitions in a single type (discriminated union) often makes this type messy: an actor often responds to message of different categories, e.g. messages sent by remote clients and messages used in internal communications. For example, an actor can spawn internal jobs and get notified by internal components. It makes sense to define these internal messages using a different (internal) type, but then actor's mailbox chages can no longer be strong type, and the actor's function look like this:
let rec loop () =
actor {
let! message = mailbox.Receive ()
match box message with
| :? PublicMessage as msg -> handlePublicMessage msg
| :? PrivateMessage as msg -> handlePrivateMessage msg
| _ -> raise (InvalidOperationException(sprintf "Invalid message type %A" message))
return! loop ()
}
loop ()
What I dislike here is that this approach takes away one of the core F# strength: type inference. Instead we have to box messages to convert them to Object type and then cast them to the types we expect.
There are two alternatives to this approach:
- Always list all message cases under the same type. Bad, bad, bad.
- Define an actor type per message type. IMHO this can turn out to be even worth because decision to create a new actor type should not be driven by language specific constraints.
I checked the code from Akka.NET bootcamp, and they are using the first approach - with message boxing and casting. Is this the best what can be done?