2
\$\begingroup\$

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
\$\endgroup\$
0

1 Answer 1

5
\$\begingroup\$

Maybe I'm missing something here - have you considered using F#'s built-in mailbox processor for your actors as you get built-in queueing of messages and guaranteed sequential messaging (i.e. not concurrent) per actor?

There's a good example here: - http://msdn.microsoft.com/en-us/library/ee370357.aspx

MailboxProcessor is a native type built into F# that allows you to post messages that get processed asynchronously from the request. They are extremely lightweight, only consuming threads whilst actually doing any work. They also guarantee thread safely in terms of if you post ten messages to it, they will get processed sequentially.

\$\endgroup\$
3
  • \$\begingroup\$ mailbox processor? Do elaborate! This was an activity just trying to create an actor system, so I don't intend to use a 3rd party Actors library, but if there is some kind of producer-consumer-queue built into F# there's no reason I couldn't use that :) \$\endgroup\$ Commented Oct 23, 2014 at 13:17
  • \$\begingroup\$ Welcome to Code Review! I don't know any F#, but here at Code Review we are trying to give clear advice for the OP code. IMO, your answer would benefit from an example of what you are talking about in what way it would benefit the op. \$\endgroup\$
    – Marc-Andre
    Commented Oct 23, 2014 at 13:44
  • \$\begingroup\$ I've added a link to a code example on MSDN. But in answer to the OP comment above, MailboxProcessors are basically Agents in F#, they are native to the language and framework, so no external libraries are required. \$\endgroup\$ Commented Oct 23, 2014 at 13:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.