Wikipedia, defines an actor as -
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.
Recently there has been some discussion in the F# open source community about an Akka like actor framework for F#. The following post introduces a very early version of the framework, in the hope that it will get some community interest and contributions.
Creating actors and sending messages
To create an actor we need to simply provide a name and a function that takes an actor instance and returns a unit Async computation
let multiplication =
(fun (actor:Actor<_>) ->
async {
let! (a,b) = actor.Receive()
let result = a * b
do printfn "%A: %d * %d = %d" actor.Path a b result
}
)
let addition =
(fun (actor:Actor<_>) ->
async {
let! (a,b) = actor.Receive()
let result = a + b
do printfn "%A: %d + %d = %d" actor.Path a b result
}
)
let calculator =
[
Actor.spawn (ActorPath.create "calculator/addition") addition
Actor.spawn (ActorPath.create "calculator/multiplication") multiplication
]
Here we have created two actors, one that carries out multiplication and one that carries out addition. To send a message to the actors we can simply get the instance and call the post method.
calculator.[0].Post((5,2))
However in an actor framework having to directly reference the actors to call post on them can be some what limiting. So when each actor is created it is assigned a path. The path is created with the following schema (actor://{machinename}/{path}). We can use this path to resolve the actor,
"actor://main-pc/calculator/addition" ?<-- (5,2)
If we know that this actor is local then we do not need to qualify the path.
"calculator/addition" ?<-- (5,2)
In addition to resolving single actors we can also broad cast to several actors at once by providing a partial path,
"calculator/" ?<-- (5,2)
Sending Messages to Remote Actors
Messages are sent via transports. Out of the box, the framework provides a TCP transport based on the high performance Fracture I/O library. Before you can send remote messages you must register a transport.
let transport = new FractureTransport({ Port = Some 8080}) :> ITransport
Remoting.registerTransport Serialisers.Binary transport
When a transport is created it is wrapped in a actor of Actor and path ‘transports/{scheme}’ in the case of the fracture transport this would be ‘transports/actor.fracture’. This actor is then supervised (see below) by the system/remotingsupervisor actor, which is initialised the a OneForOne strategy so will restart the transport if it errors at any point. Once a transport is registered we can then send a message to a remote actor by using a fully qualified actor path, for example,
"actor.fracture://10.256.76.94:8080/calculator/addition" ?<-- (5,2)
notice here that the scheme selects which transport the message will be sent on. If you wanted to send a message on another registered transport (http for example), the path would be,
"actor.http://10.256.76.94:8082/calculator/addition" ?<-- (5,2)
Supervisors
Supervisors, provide a way to monitor make decisions about what to do when a actor errors. How the supervisor responds when an actor receives an error depends on the strategy provided (for more info see here.
To create a supervised actor, we can simply do the follwoing
let err =
(fun (actor:Actor<string>) ->
async {
let! msg = actor.Receive()
if msg <> "fail"
then printfn "%s" msg
else failwithf "ERRRROROROR"
}
) |> Actor.spawn (ActorPath.create "err_0")
let supervisor = Actor.supervisor (ActorPath.create "oneforone") Supervisor.OneForOne [err]
if we now post ‘fail’ to this actor it will throw an exception and the supervisor will step in and restart the actor.
Conclusion
This has just been a brief introduction into the framework, there is much more in the pipeline the project is in its very early stages. I welcome anyone who wants to contribute and shape this into a fully fledged actor framework. So basically rip it apart tell let me know what you like don’t like, send pull requests raise bugs, feature requests, improve documentation. Any contribution will be welcomed.
The source for this can be found on GitHub here. And more complete documentation here