Any thoughts on my actor model here? Obviously it doesn't have implementation for concurrency as it stands- being completely synchronous, but it should be easy to make the send/become/create be sent to a thread pool without effecting the overall API, no? All state for each actor is local and immutable only through become
do you change your state, and that's only for your next message processing. I suppose I should make the send push to a queue so message processing per actor is synchronous...
Thoughts?
Mostly I want review of the actor system interface and concept, the rest of the code is just usage example, though some thoughts on the example is still nice - helps me improve my F#.
The randomness is terrible - is my chooseAttack
causing some value caching because the input variables don't change? Can anyone think of a better way to implement random attack choosing here off hand?
I'm thinking of putting the attacker/defender on the attack itself so that the fighter doesn't have a hardcoded target, but rather responds to who attacks- but the actual fighting system is kind of an arbitrary example I tossed together to try out the general actor system concept.
let undefined<'T> : 'T = failwith "Not implemented yet" // just a bottom value
type ActorSystem<'addr, 'msg> = {
send: ('addr -> 'msg -> ActorSystem<'addr, 'msg>)
become: ('addr -> (ActorSystem<'addr, 'msg> -> 'msg -> ActorSystem<'addr, 'msg>) -> ActorSystem<'addr, 'msg>)
create: ('addr -> (ActorSystem<'addr, 'msg> -> 'msg -> ActorSystem<'addr, 'msg>) -> ActorSystem<'addr, 'msg>)
}
let actorSystem<'addr, 'msg> =
let _currentActors = new System.Collections.Concurrent.ConcurrentDictionary<'addr, (ActorSystem<'addr, 'msg> -> 'msg -> ActorSystem<'addr, 'msg>)>()
let rec _send addr msg = _currentActors.[addr] { send = _send; become = _become; create = _create } msg
and _become addr actor =
_currentActors.TryUpdate(addr, actor, _currentActors.[addr])
{ send = _send; become = _become; create = _create }
and _create addr actor =
ignore(_currentActors.TryAdd(addr, actor))
{ send = _send; become = _become; create = _create }
{ send = _send; become = _become; create = _create }
let send addr msg sys = sys.send addr msg
let become addr actor sys = sys.become addr actor
let create addr actor sys = sys.create addr actor
///////////////////
// Everything below here is example of usage, mostly want review for the interface
// and general concept defined above. Ideas where I should put the failure-handler?
// Should I make it a parameter on the actorSystem function at system creation?
// Then send/other functions could work with it when failures occur, or ?
///////////////////
type Target = Face | Stomach | Air
type Attack = Punch of Target | Kick of Target
let calculateDamage attack =
let damageForLocation location = match location with | Face -> 30 | Stomach -> 10 | _ -> 0
match attack with | Punch place -> 1 * (damageForLocation place) | Kick place -> 2 * (damageForLocation place)
let chooseAttack = fun _ ->
let rng = new System.Random()
let target = match rng.Next(999) % 3 with | 0 -> Face | 1 -> Stomach | _ -> Air
match rng.Next(1000) % 2 with | 0 -> Punch target | _ -> Kick target
let printAttackActor attacker defender =
fun actors attack ->
printfn "%s" <| attacker +
match attack with
| Punch Face -> " punches " + defender + " in his ugly face!"
| Kick Face -> " tries to knock " + defender + "'s pumpkin off!"
| Punch Stomach -> " gulletfists " + defender + "!"
| Kick Stomach -> " stuffs a boot in " + defender + "'s gut!"
| Punch Air -> " shows off his awesome punching at " + defender + "!"
| Kick Air -> " kicks over " + defender + "'s head!"
actors
let printDefendActor defender =
fun actors attack ->
printfn "%s" <| defender +
match attack with
| Punch Face -> " bleeds from the ears."
| Kick Face -> " sniffs blood and rubber."
| Punch Stomach -> " flexes his abs of blubber, it's ineffective!"
| Kick Stomach -> " spits up bile."
| Punch Air -> " is not impressed!"
| Kick Air -> " is shaking in his boots!"
actors
let rec fighter name opponent health nextAttack =
fun actors attack ->
actors |>
create (name+opponent+"printDefend") (printDefendActor name) |>
create (name+opponent+"printAttack") (printAttackActor name opponent) |>
become name (fun actors attack ->
let newHealthValue = health - calculateDamage attack
if newHealthValue >= 0 then
actors |>
send (name+opponent+"printDefend") attack |>
send (name+opponent+"printAttack") nextAttack |>
become name (fighter name opponent newHealthValue <| chooseAttack()) |>
send opponent nextAttack
else // He's dead Jim.
printfn "%s" ("the corpse of " + name + " bleeds on the ground")
actors |>
become name (fun actors _ ->
printfn "%s" ("the corpse of " + name + " bleeds on the ground")
actors)) |>
send name attack
[<EntryPoint>]
let main argv =
actorSystem<string, Attack> |>
create "Bruce Willis" (fighter "Bruce Willis" "Matt Damon" 100 <| chooseAttack()) |>
create "Matt Damon" (fighter "Matt Damon" "Bruce Willis" 100 <| chooseAttack()) |>
send "Bruce Willis" (Punch Air) |>
ignore
printfn "press enter to quit."
System.Console.ReadLine() |> ignore
0 // return an integer exit code