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"
Full name: Actor.multiplication
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<_>
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
abstract member IActor.Receive : int option -> Async<'a * IActor option>
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Full name: Actor.addition
Full name: Actor.calculator
from FSharp.Actor
Full name: FSharp.Actor.Actor.spawn
{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<_>
Full name: Actor.schizoPing
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<_>
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
Full name: Actor.schizo
Full name: Actor.child
Full name: Actor.parent
Full name: FSharp.Actor.Actor.spawnLinked
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<_>
Full name: Microsoft.FSharp.Collections.List.init
Full name: FSharp.Actor.Actor.unlink