Exercises in Programming Style–Actors

NOTE : read the rest of the series, or check out the source code.

If you enjoy read­ing these exer­cises then please buy Crista’s book to sup­port her work.

exercises-prog-styles-cover

Fol­low­ing on from the last post, we will look at the Actors style today.

 

Style 28 – Actors

Constraints

  • The larg­er prob­lem is decom­posed into ‘things’ that make sense for the prob­lem domain.
  • Each ‘thing’ has a queue meant for oth­er things to place mes­sages in it.
  • Each ‘thing’ is a cap­sule of data that expos­es only its abil­i­ty to receive mes­sages via the queue.
  • Each ‘thing’ has its own thread of exe­cu­tion inde­pen­dent of the oth­ers.

 

I have been look­ing for­ward to doing this style every since the Let­ter­box style months ago  So with­out any delay, let’s get start­ed!

F# has built-in sup­port for the Actor Mod­el via the Mail­box­Proces­sor type, and we’ll use it to rep­re­sent each of the ‘things’ in our solu­tion here.

First, we’ll cre­ate an actor to load and store the words that we need to process. Since Mail­box­Proces­sor is gener­ic, we can define what mes­sages it can receive with a DataS­tor­ageMes­sage union type that allows two kinds of mes­sages:

  • Load­Words — which con­tains the path to the local file to load words from
  • NextWord — once loaded, this mes­sage fetch­es the next avail­able word

In the snip­pet below, you can see that we have a recur­sive receive loop which asyn­chro­nous­ly waits for mes­sage to arrive in its mail­box and han­dles them accord­ing­ly. One thing to note here is that, if a Load­Words mes­sage is received before all the loaded words are processed the cur­rent behav­iour is to over­ride the remain­ing words list. This can give you unex­pect­ed results, the cur­rent design leaves the respon­si­bil­i­ty of ensur­ing this doesn’t hap­pen with high­er lev­el abstrac­tions (see the con­troller actor below).

Style28-01

Next, we’ll add anoth­er actor to man­age the stop words. Sim­i­lar to the dataS­tor­age­M­an­ag­er, we’ll first define the mes­sages that can be sent to our actor:

  • Load­Stop­Words — which con­tains the path to the local file to load stop words from
  • IsStop­Word — which pass­es in a word and expects a boolean as reply

As we did in the dataS­tor­age­M­an­ag­er, when­ev­er the stop­Words­Man­ag­er actor receives a Load­Stop­Words mes­sage it’ll replace the cur­rent list of stop words. All future words passed in via the IsStop­Word mes­sage will be checked against the updat­ed list of stop words.

Style28-02

Next, we’ll add an actor to track the word fre­quen­cies. This actor can accept three mes­sages:

  • Add — adds anoth­er word to the cur­rent word fre­quen­cies
  • TopN — fetch­es the cur­rent top N words with their cor­re­spond­ing fre­quen­cies
  • Reset — resets the count

Again, the code snip­pet below should be pret­ty straight for­ward, although there is actu­al­ly a frailty here due to the use of Seq.take. One thing that often annoys me about Seq.take is that, unlike Enumerable.Take, it throws when there is insuf­fi­cient num­ber of ele­ments in the sequence! In this case, if the call­ing agent ask for TopN too ear­ly or with a large N it’s pos­si­ble to kill our actor (which is also some­thing that we’re not han­dling here). It’s fine giv­en the con­text of this exer­cise, but these are things that you need to con­sid­er when writ­ing pro­duc­tion-ready code.

Style28-03

Last­ly, we’ll add an actor to orches­trate the con­trol flow of our pro­gram, that will accept a sim­ple Run mes­sage. Here, I’ve added an AsyncReplyChannel<unit> to the Run mes­sage so that the caller has a deter­min­is­tic way to know when the pro­gram has com­plet­ed.

When the con­troller receives a Run mes­sage, it’ll ini­tial­ize the oth­er actors (which hap­pens con­cur­rent­ly due to the asyn­chro­nous nature of mes­sag­ing) and then process all the words from Pride and Prej­u­dice by recur­sive­ly fetch words from the dataS­tor­age­M­an­ag­er until there’s no more. One thing to note is that, because a Mail­box­Proces­sor process mes­sages one-at-a-time, so even if the con­troller receives mul­ti­ple Run mes­sages at the same time it’ll still process them one at a time and we don’t even have to use locks!

Style28-04

 

To run our pro­gram, we’ll kick things off by send­ing a Run mes­sage to the con­troller. I opt­ed to run this syn­chro­nous­ly, but you could just eas­i­ly ignore the reply and run the pro­gram asyn­chro­nous­ly with Async.Ignore and Async.Start.

Style28-05

 

I’m a mas­sive fan of Erlang and the Actor Mod­el, they’re high­ly relat­ed since Erlang imple­ments the Actor Mod­el but shouldn’t be mixed up — e.g. code hot swap­ping and super­vi­sion trees are fea­tures of Erlang and Erlang OTP, and are not pre­scribed as part of the Actor Mod­el (which is a the­o­ret­i­cal mod­el for describ­ing com­pu­ta­tion).

If you haven’t already, please go ahead and watch this Channel9 record­ing of a con­ver­sa­tion between Carl Hewitt and Erik Mei­jer on the Actor Mod­el — what it is, and what it isn’t. I also did a write up to sum­marise the key points.


 

You can find the source code for this exer­cise here.