Fsharp.Actor


Actors

An actor is a computational entity that, in response to a message it receives, can concurrently:

  • Send a finite number of messages to other actors.
  • Create a finite number of new actors.
  • Designate the behavior to be used for the next message it receives.

as defined by wikipedia

let multiplication = 
    (fun (actor:IActor<_>) ->
        let rec loop() =
            async {
                let! ((a,b), sender) = actor.Receive()
                let result = a * b
                do printfn "%A: %d * %d = %d" actor.Path a b result
                return! loop()
            }
        loop()
    )

let addition = 
    (fun (actor:IActor<_>) ->
        let rec loop() =
            async {
                let! ((a,b), sender) = actor.Receive()
                let result = a + b
                do printfn "%A: %d + %d = %d" actor.Path a b result
                return! loop()
            }
        loop()
    )

let calculator = 
    [
       Actor.spawn (Actor.Options.Create("calculator/addition")) addition
       Actor.spawn (Actor.Options.Create("calculator/multiplication")) multiplication
    ]

// The above code creates two actors `calcualtor/addition` and `calculator/multiplication`
calculator/addition pre-start Status: Shutdown "Initial Startup"
calculator/addition started Status: Running "Initial Startup"
calculator/multiplication pre-start Status: Shutdown "Initial Startup"
calculator/multiplication started Status: Running "Initial Startup"
    
val multiplication : actor:FSharp.Actor.Actor<int * int> -> Async<unit>
val addition : actor:FSharp.Actor.Actor<int * int> -> Async<unit>
val calculator : FSharp.Actor.ActorRef list =
    [calculator/addition; calculator/multiplication]

We can see that the actors state transitions are logged.

Once we have created our actors we can be looked up by their path

"calculator/addition" ?<-- (5,2)
"calculator/multiplication" ?<-- (5,2)

// Sending both of these messages yields
actor://main-pc/calculator/addition: 5 + 2 = 7
actor://main-pc/calculator/multiplication: 5 * 2 = 10

We can also send messages directly to actors if we have their ActorRef

calculator.[0] <-- (5,2)

// This also yields 
actor://main-pc/calculator/addition: 5 + 2 = 7

Or we could have broadcast to all of the actors in that collection

calculator <-* (5,2)

// This also yields 
actor://main-pc/calculator/addition: 5 + 2 = 7
actor://main-pc/calculator/multiplication: 5 * 2 = 10

We can also resolve systems of actors.

"calculator" ?<-- (5,2)

// This also yields 
actor://main-pc/calculator/addition: 5 + 2 = 7
actor://main-pc/calculator/multiplication: 5 * 2 = 10

However this actor wont be found because it does not exist

"calculator/addition/foo" ?<-- (5,2)

// resulting in a `KeyNotFoundException`
System.Collections.Generic.KeyNotFoundException: Could not find actor calculator/addition/foo

We can also kill actors

calculator.[1] <!- (Shutdown("Cause I want to"))
// or
"calculator/addition" ?<!- (Shutdown("Cause I want to"))

// Sending now sending any message to the actor will result in an exception 
System.InvalidOperationException: Actor (actor://main-pc/calculator/addition) could not handle message, State: Shutdown

Changing the behaviour of actors

You can change the behaviour of actors at runtime. This achieved through mutually recursive functions

let rec schizoPing = 
    (fun (actor:IActor<_>) ->
        let log = (actor :?> Actor.T<_>).Log
        let rec ping() = 
            async {
                let! (msg,_) = actor.Receive()
                log.Info(sprintf "(%A): %A ping" actor msg, None)
                return! pong()
            }
        and pong() =
            async {
                let! (msg,_) = actor.Receive()
                log.Info(sprintf "(%A): %A pong" actor msg, None)
                return! ping()
            }
        ping()
    )
        

let schizo = Actor.spawn (Actor.Options.Create("schizo")) schizoPing 

!!"schizo" <-- "Hello"

// Sending two messages to the 'schizo' actor results:
(schizo): "Hello" ping
(schizo): "Hello" pong

Linking Actors

Linking an actor to another means that this actor will become a sibling of the other actor. This means that we can create relationships among actors

let child i = 
    Actor.spawn (Actor.Options.Create(sprintf "a/child_%d" i)) 
         (fun actor ->
             let log = (actor :?> Actor.T<_>).Log 
             let rec loop() =
                async { 
                   let! msg = actor.Receive()
                   log.Info(sprintf "%A recieved %A" actor msg, None) 
                   return! loop()
                }
             loop()
         )

let parent = 
    Actor.spawnLinked (Actor.Options.Create "a/parent") (List.init 5 (child))
            (fun actor -> 
                let rec loop() =
                  async { 
                      let! msg = actor.Receive()
                      actor.Children <-* msg
                      return! loop()
                  }
                loop()    
            ) 

parent <-- "Forward this to your children"

// This outputs
actor://main-pc/a/child_1 recieved "Forward this to your children"
actor://main-pc/a/child_3 recieved "Forward this to your children"
actor://main-pc/a/child_2 recieved "Forward this to your children"
actor://main-pc/a/child_4 recieved "Forward this to your children"
actor://main-pc/a/child_0 recieved "Forward this to your children"

We can also unlink actors

Actor.unlink !*"a/child_0" parent

parent <-- "Forward this to your children"

// This outputs
actor://main-pc/a/child_1 recieved "Forward this to your children"
actor://main-pc/a/child_3 recieved "Forward this to your children"
actor://main-pc/a/child_2 recieved "Forward this to your children"
actor://main-pc/a/child_4 recieved "Forward this to your children"
val multiplication : actor:IActor<int * int> -> Async<'a>

Full name: Actor.multiplication
val actor : IActor<int * int>
Multiple items
type IActor =
  interface
    abstract member Link : IActor -> unit
    abstract member Post : obj * IActor option -> unit
    abstract member PostSystemMessage : SystemMessage * IActor option -> unit
    abstract member Start : unit -> unit
    abstract member UnLink : IActor -> unit
    abstract member UnWatch : unit -> unit
    abstract member Watch : IActor -> unit
    abstract member add_OnRestarted : Handler<IActor> -> unit
    abstract member add_OnStarted : Handler<IActor> -> unit
    abstract member add_OnStopped : Handler<IActor> -> unit
    ...
  end

Full name: FSharp.Actor.Types.IActor

--------------------
type IActor<'a> =
  interface
    inherit IActor
    abstract member Post : 'a -> unit
    abstract member Post : 'a * IActor option -> unit
    abstract member Receive : int option -> Async<'a * IActor option>
    abstract member Receive : unit -> Async<'a * IActor option>
  end

Full name: FSharp.Actor.Types.IActor<_>
val loop : (unit -> Async<'b>)
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val a : int
val b : int
val sender : IActor option
abstract member IActor.Receive : unit -> Async<'a * IActor option>
abstract member IActor.Receive : int option -> Async<'a * IActor option>
val result : int
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property IActor.Path: ActorPath
val addition : actor:IActor<int * int> -> Async<'a>

Full name: Actor.addition
val calculator : IActor list

Full name: Actor.calculator
module Actor

from FSharp.Actor
val spawn : options:Actor.Options<'a> -> computation:(IActor<'a> -> Async<unit>) -> IActor

Full name: FSharp.Actor.Actor.spawn
type Options<'a> =
  {Id: string;
   Mailbox: IMailbox<ActorMessage<'a>>;
   Supervisor: IActor<SupervisorMessage> option;
   Logger: ILogger;
   Path: ActorPath;}
  static member Create : ?id:string * ?mailbox:IMailbox<ActorMessage<'a>> * ?supervisor:IActor<SupervisorMessage> * ?logger:ILogger * ?address:ActorPath -> Options<'a>
  static member Default : Options<'a>

Full name: FSharp.Actor.Actor.Options<_>
static member Actor.Options.Create : ?id:string * ?mailbox:IMailbox<ActorMessage<'a>> * ?supervisor:IActor<SupervisorMessage> * ?logger:ILogger * ?address:ActorPath -> Actor.Options<'a>
union case SystemMessage.Shutdown: string -> SystemMessage
val schizoPing : actor:IActor<'a> -> Async<'b>

Full name: Actor.schizoPing
val actor : IActor<'a>
val log : ILogger
type T<'a> =
  interface IActor<'a>
  private new : computation:(IActor<'a> -> Async<unit>) * ?options:Options<'a> -> T<'a>
  override Equals : y:obj -> bool
  override GetHashCode : unit -> int
  override ToString : unit -> string
  member Log : ILogger

Full name: FSharp.Actor.Actor.T<_>
val ping : (unit -> Async<'c>)
val msg : 'a
abstract member ILogger.Info : string * exn option -> unit
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
union case Option.None: Option<'T>
val pong : (unit -> Async<'c>)
val schizo : IActor

Full name: Actor.schizo
val child : i:int -> IActor

Full name: Actor.child
val i : int
val actor : IActor<obj>
val loop : (unit -> Async<'a>)
val msg : obj * IActor option
val parent : IActor

Full name: Actor.parent
val spawnLinked : options:Actor.Options<'a> -> linkees:seq<IActor> -> computation:(IActor<'a> -> Async<unit>) -> IActor

Full name: FSharp.Actor.Actor.spawnLinked
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val init : length:int -> initializer:(int -> 'T) -> 'T list

Full name: Microsoft.FSharp.Collections.List.init
property IActor.Children: seq<IActor>
val unlink : linkees:seq<IActor> -> actor:IActor -> IActor

Full name: FSharp.Actor.Actor.unlink
Fork me on GitHub