2

Akka and its Akka.NET port support switchable behaviors (http://getakka.net/docs/working-with-actors/Switchable%20Behaviors), and not just in a simple form of letting an actor to become an actor with different behavior but also in a form of behavioral stack: an actor can go back to its previous behavior. This is achieved with methods BecomeStacked and BecomeUnstacked.

In F# behavior is supported in a different way: there is no need for Become method, instead an actor function can define multiple mailbox processing handlers and call a different handler effectively switching an actor to a different behavior. This works fine for non-stacked behaviors, but what if an actor function needs to stack its behaviors? How can this be implemented in F#?

UPDATE. To illustrate with use case. Consider telephony where an actor can have behaviors DirectCall, ConferenceCall and OnHold. OnHold behavior is reachable from both DirectCall and ConferenceCall, and whil OnHold an actor can receive messages to start nested calls. When a nested call is completed, an actor goes back to a previous call which can be either DirectCall or ConferenceCall. I can't see how this can be implemented without explicitly sending complete stack to an actor. After all, it's not that bad, I was just wondering if there is another way offered by Akka.NET. The mailbox Context.BecomeXXX is still available in F# API, but it has no use from within "actor" computational expression.

UPDATE 2 I have to agree with Tomas Jansson who reasoned in comments that he'd rather send the stack of previous behaviors (or previous states let's be fair) explicitly instead of relying on Akka's built-it BecomeStacked method that hides the history and makes it harder to test. So the absence of BecomeXXX methods in F# API doesn't really make it less powerful.

Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100

1 Answers1

1

I wrote a simple sample of how I would solve it using MailboxProcessor, the concept is the same so you should be able to translate that easily to akka.net:

type Message = 
    | Ping
    | Back

type State = {Behavior: State -> Message -> State; Previous: State -> Message -> State}

let rec pong state message =
    match message with 
    | Ping -> 
        printfn "Pong"
        {state with Behavior = superpong} 
    | Back ->
        {state with Behavior = state.Previous}
and superpong state message = 
    match message with 
    | Ping ->
        printfn "Super pong"
        {state with Behavior = pong} 
    | Back ->
        {state with Behavior = state.Previous}
        
let agent = new MailboxProcessor<Message>(fun inbox ->
    let setPrevious oldState newState = 
        {newState with Previous = oldState.Behavior}
    let rec loop state = 
        async {
            let! msg = inbox.Receive()
            return! loop (msg |> state.Behavior state |> setPrevious state)
        }
    loop {Behavior = pong; Previous = pong})
    
agent.Start()
agent.Post(Ping)
agent.Post(Ping)
agent.Post(Back)
agent.Post(Ping)
agent.Post(Ping)

Output is:

Pong

Super pong

Super pong

Pong

The idea is that you have the behavior as part of the state to the agent. Note that the functions must be defined with the let rec ... and ... syntax so that the second function can return the first function in a new state.

Update simplified the example with only one message type.

Community
  • 1
  • 1
Tomas Jansson
  • 22,767
  • 13
  • 83
  • 137
  • But what if in addition to "pong" and "superpong" you have "extrapong" and in addition to "Switch" you have "Back" that brings you back to the previous behavior? Support for this is available in Akka.NET C#. – Vagif Abilov Jan 15 '16 at 11:01
  • In other words, what you've shown is a regular Akka Become, not BecomeStacked. – Vagif Abilov Jan 15 '16 at 11:07
  • Can you go back an arbitrarly number of steps or just to the previous? Either way I would define it in the state type. You could for example to `type State = {Behavior: State -> Message -> State; Previous: State -> Message -> State}` and then pipe the result from `(msg |> state.Behavior state)` to something that sets the `Previous`. Updated the example so show what I mean. – Tomas Jansson Jan 15 '16 at 11:09
  • Most likely, because I don't see the use case of `BecomeStacked`. That is a behavior one should now when writing an actor, or am I wrong? – Tomas Jansson Jan 15 '16 at 11:10
  • If it's a stack then any number of previous steps must be supported. – Vagif Abilov Jan 15 '16 at 11:11
  • Just store those in a stack in the state and you're good to go – Tomas Jansson Jan 15 '16 at 11:12
  • Sure but then my message must contain the whole stack, i.e. implement part of you get for free in Akka. – Vagif Abilov Jan 15 '16 at 11:13
  • No, your message doesn't contain the stack that is something the state of the actor holds on to. I think this is the only way to do it without using mutation. – Tomas Jansson Jan 15 '16 at 11:14
  • When you do a Push or Pop on the stack is up to you, you don't need messages for that either. That could be decided by some logic. – Tomas Jansson Jan 15 '16 at 11:14
  • I updated the post with a use case that is more or less from real life. – Vagif Abilov Jan 15 '16 at 11:17
  • Correct, you don't need to include stack in messages, but you need to send complete stack to an actor function. – Vagif Abilov Jan 15 '16 at 11:19
  • I would actually rather have it the way I did in the example since it is pure and easier to test. Implementing something like `BecomeStacked` sound like it will mutate the state of the actor and make it harder to test. – Tomas Jansson Jan 15 '16 at 11:39