Actor Model in Prolog: Ping Pong and Sieve


It is less known that Prolog has a proposal for threads and messages queues. This draft started in 2006 and had Paulo Moura as editor. It goes by the identification ISO/IEC DTR 13211-5:2007 and defines some predicates for threads, message queues and locks.

What is probably even lesser known that these predicates can be used to realize the actor model. We made a corresponding experiment inspired by discussion with Torbjörn Lager. It transpired that the Erlang receive can be realized with clause references.

Our take is not only inspired by Erlang but also by the Go programming language. Besides Erlang inspired send and receive, the library(experiment/broker) also provides a run primitive. This is very similar like the go command in the Go programming language expect that we can specify a broker on another machine.

Communication between machines is currently done via UDP. In this protocol we can send datagrams between machines but do not have any guarantees. There is no loss for our experiments, since in small and uncongested networks most datagrams arrive. Using the library(experiment/broker) we can implement the classic ping pong example as follows:

ping(0, PongPID) :- !,
   send(PongPID, finished),
   write('Ping finished'), nl.
ping(N, PongPID) :-
   self(Self),
   send(PongPID, ping(Self)),
   receive(pong, _),
   write('Ping received pong'), nl,
   N1 is N-1,
   ping(N1, PongPID).

pong :-
   receive((finished; ping(PingPID)), M),
   (M == finished -> write('Pong finished'), nl;
    write('Pong received ping'), nl,
    send(PingPID, pong), pong).

We made an experiment with two Android phones. If the wifi adapter is switched on and if the IP address of the wifi adapter is specified on both devices, we could run the ping pong example across these Android phones. This is the result when both the ping actor and the pong actor are started:

As next we tried the sieve of Erastothenes. In this example the spawn/3 command is put to a test since the example uses a pipeline that extends itself for every new prime number that is encountered. Using the library(experiment/broker) we can implement the parallel sieve example as follows:

source(L, H, P) :- L =< H, !,
   send(P, number(L)),
   L2 is L+1,
   source(L2, H, P).
source(_, _, P) :-
   send(P, finish).

divider(D, P) :-
  receive((number(L); finish), S),
  (S = number(L) ->
      (L mod D =\= 0 -> send(P, number(L)); true),
      divider(D, P);
      send(P, finish)).

sink :-
   receive((number(L); finish), S),
   (S = number(L) ->
     write('number '), write(L), nl,
     spawn('localhost:3010', sink, P),
     divider(L, P);
      write('finish'), nl).

To run the example, we need first start the sink via spawn/3 so as to obtain the actor path of the sink. We can then start the source via run/2. Since the actors hand a finish message and not only a number(_) messages, the pipeline will terminate when the high number is reached. Here is an example run:

The ping pong example will only run with the upcoming release 1.4.2 of Jekejeke Prolog since we needed to fix a glitch in our message queues. But we already published the code to GitHub. It should be also noted that we use much shorter and simpler name "pipe" and not "message_queue" as in the ISO multi-threading support draft.

ISO/IEC DTR 13211-5:2007 - Prolog multi-threading support
https://logtalk.org/plstd/threads.pdf

Open Source: Module "broker"
https://github.com/jburse/jekejeke-devel/blob/master/jekrun/headless/jekpro/frequent/experiment/broker.p