5

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:

  1. Always list all message cases under the same type. Bad, bad, bad.
  2. 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?

Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100
  • I have no experience with Akka.NET. If the only concern is mixing up public and private message types, are you allowed to use a nested discriminated union? ie. type Message = Public of PublicMsg | Private of PrivateMsg; where PublicMsg and PrivateMsg are discriminated unions of their own. – piaste Apr 07 '16 at 21:38

1 Answers1

0

Ok, seems to me there are two problems - internal messages and boxing/type safety.

As I understand the actor model, there are no private messages. Either an actor can respond to a message, or it can't. If you really want to have an asynchronous private logic, I suggest using an async function during the processing of a public message, not a different actor message as it would have to be public.

For type safety, although I have not tried it yet, there seem to be another way in avoiding the computation expression to spawn actors and using provided generic actorOf function instead like this:

let handleMessage message =
    match message with
    | PublicMessage as msg -> handlePublicMessage msg
    | PrivateMessage as msg -> handlePrivateMessage msg
    | _ -> raise (InvalidOperationException(sprintf "Invalid message type %A" message))

let actorRef = spawn system "my-actor" <| actorOf handleMessage

It is basically a type-safe alternative to your option 1. I personally don't see a problem in listing all supported message types in one union type. Either the actor knows its messages (with a default fallback preferably), or you want to go full object-oriented and there are no types, only boxed dynamic messages.

More information for example here in the documentation: http://getakka.net/docs/FSharp%20API#creating-actors-with-actor-computation-expression

Honza Brestan
  • 10,637
  • 2
  • 32
  • 43
  • Thanks. I was aware of actorOf and actorOf2 but they don't really solve the prolem of message downcast in case an actor should handle more than one message type. I also discussed this on Akka.NET gitter chat, and conclusion was that the limitation lies in F# language capabilities. – Vagif Abilov Apr 13 '16 at 14:18