Mikhail Shilkov
Mikhail Shilkov

Reputation: 35124

How to avoid mutable reference to Actor instance

I'm trying to gain some experience with Akka.NET actors in F#. I have the scenario when one actor needs to spin up another actor as its child. The first actor would convert each message and then send the result to the other actor. I use actorOf2 function to spawn actors. Here is my code:

let actor1 work1 work2 =
  let mutable actor2Ref = null
  let imp (mailbox : Actor<'a>) msg =
    let result = work1 msg
    if actor2Ref = null then 
      actor2Ref <- spawn mailbox.Context "decide-actor" (actorOf2 <| work2)
    actor2Ref <! result
  imp

let actor1Ref = actor1 work1' work2'
  |> actorOf2 
  |> spawn system "my-actor"

What I don't like is the mutable actor reference. But I had to make it mutable because I need mailbox.Context to spawn a child actor, and I don't have any context before the first call. I saw this question but I don't know how to apply it to my function.

In a more advanced scenario I need a collection of child actors which is partitioned by a key. I'm using a Dictionary of actor refs in this scenario. Is there a better (more F#-ish) way?

Upvotes: 3

Views: 186

Answers (2)

Fyodor Soikin
Fyodor Soikin

Reputation: 80714

In order to keep your "state" across iterations, you need to make the iterations explicit. That way, you can pass the current "state" as tail call argument. Just as in the question you linked:

let actor1 work1 work2 (mailbox : Actor<'a>) =
  let rec imp actor2 =
    actor {
      let! msg = mailbox.Receive()
      let result = work1 msg

      let actor2 =
        match actor2 with
        | Some a -> a // Already spawned on a previous iteration
        | None -> spawn mailbox.Context "decide-actor" (actorOf2 <| work2)

      actor2 <! result
      return! imp (Some actor2)
    }

  imp None

And now, you don't need to use actorOf2 or actorOf for spawning this actor, because it already has the right signature:

let actor1Ref = 
  actor1 work1' work2'
  |> spawn system "my-actor"

.
EDIT
If you're concerned about the extra boilerplate, nothing prevents you from packing the boilerplate away as a function (after all, actorOf2 does something similar):

let actorOfWithState (f: Actor<'msg> -> 'state -> 'msg -> 'state) (initialState: 'state) mailbox =
  let rec imp state =
    actor {
      let! msg = mailbox.Receive()
      let newState = f mailbox state msg
      return! imp newState
    }

  imp initialState

And then:

let actor1 work1 work2 (mailbox : Actor<'a>) actor2 msg =
  let result = work1 msg
  let actor2 =
    match actor2 with 
    | Some a -> a
    | None -> spawn mailbox.Context "decide-actor" (actorOf2 work2)

  actor2 <! result
  actor2

let actor1Ref = 
  actor1 work1' work2'
  |> actorOfWithState
  |> spawn system "my-actor"

Upvotes: 3

Joel Mueller
Joel Mueller

Reputation: 28735

You could do something along these lines, and just not store a reference to the child actor at all, because the Context is already doing that for you.

let actor =
    let ar = mailbox.Context.Child(actorName)
    if ar.IsNobody() then
        spawn mailbox.Context actorName handler
    else ar

If the Context.Child lookup turns out to be too slow, creating a memoized function that keeps mutability hidden from the other code would be pretty easy to do.

Upvotes: 2

Related Questions