Programming Shootout - Objects vs Boxes vs Actors vs Agents


Posted on February 3, 2014

Four Programming Paradigms

This post uses a simple calculator specification to demonstrate four programming models. This is a follow-up to my previous post on the agent oriented programming. TL;DR: agent oriented programming is an interesting idea, but does impose burden on the programmer. Let’s take agents, with Wikipedia definitions of three other models, to compare:

  • Objects object oriented programming represents concepts as “objects” that have data fields and associated procedures known as methods. Objects, which are usually instances of classes, are used to interact with one another to design applications and computer programs.

  • Boxes the box calculus is defined in (G Grov, 2007). Finite state automata are used to structure communicating programs into a series of “boxes”, where each box maps inputs to outputs. Boxes are linked by wires and controlled by generalised transitions.

  • Actors the actor model is a mathematical model of concurrent computation that treats “actors” as the universal primitives of concurrent digital computation. In response to a message that it receives, an actor can make local decisions, create more actors, send more messages, and determine how to respond to the next message received.

  • Agents agent-oriented programming centres on the concept of software agents, it has externally specified agents at its core. They can be thought of as abstractions of objects. Exchanged messages are interpreted by receiving “agents”, in a way specific to its class of agents.

Six Implementations

Here are the six implementations under review:

Basic UnitRealisationSpecial Features
C++(Functional) objectsLanguageMulti-paradigm, adds object orientation to C.
HaskellLanguageGHC, supports [many extensions][ghc-extensions] to Haskell 2010.
HumeBoxesLanguageTargets embedded systems, C and VHDL backends.
ErlangActorsLanguageScalable VM with fault tolerance built in.
eXATAgentsErlang libraryErlang library implementation of FIPA agent standard.
JadeJava libraryJava library implementation of FIPA standard.

The Calculator Specification

calculator : {inc:Nat → Unit, dec:Nat → Unit, get:Unit → Nat}
calculator = let x = 1 in {inc = λy:Nat. x:=x+y, dec = λy:Nat. x:=x-y, get = λ_:Unit. !x };

Each implementation is split in to two basic units. One is a calculator, with a simple API of inc, dec and get. The other is a client that uses the API to modify the internal state of the calculator. The API is defined as follows:

The calculator’s internal state is an integer with the initial value of 0. The client makes four sequential calls to the calculator’s API: inc 4 inc 2 dec 1 and get. The client receives a token containing the value 5, reflecting the final internal state of the calculator.

The Shootout…

Objects in C++

C++ is a general purpose statically typed and multi-paradigm programming language that adds object orientation to C, and classes are the central feature of C++. The example below defines a Calculator class, which is instantiated and its internal state modified within the Client program.

Calculator.h
class Box { private: int x; public: Box(); void inc(int y); void dec(int y); int get();
};
Calculator.cpp
#include "Calculator.h" Calculator::Calculator () { x = 0; }
void Calculator::inc (int y) { x += y; }
void Calculator::dec (int y) { x -= y; }
int Calculator::get () { return x; }
Client.cpp
#include "Calculator.h"
#include 
using namespace std; int main () { Calculator calc; calc.inc(4); calc.inc(2); calc.dec(1); printf("Answer: %d\n",calc.get()); return 0;
} 

Functional Objects in Haskell

Haskell is a general purpose non-strict purely functional programming language. The Haskell version of the calculator has been implemented deliberately with mutable functional objects using an MVar, to mimic the object model of the C++ example above. The Calculator data type nicely reflects the types expressed in the calculator specification.

Calculator.hs
module Calculator (Calculator(..),newCalculator) where
import Control.Concurrent.MVar -- | The calculator API
data Calculator = Calculator { inc :: Int -> IO () , dec :: Int -> IO () , get :: IO Int } newCalculator :: IO Calculator
newCalculator = do mv <-> modifyMVar_ mv (\x -> return (x+y)) , dec = \y -> modifyMVar_ mv (\x -> return (x-y)) , get = readMVar mv }
Client.hs
module Client where import Calculator main :: IO ()
main = do calculator <->>= \x -> print $ "Answer: " ++ show x 

Boxes in Hume

Hume is a novel strongly-typed functionally inspired programming language developed at Heriot-Watt and St. Andrews Universities, intended for resource bounded domains to target real-time embedded platforms and safety critical systems. It is based on concurrent finite state automata controlled by pattern matching and recursive functions over rich types. The language toolkit includes a Hume interpreter and Hume Abstract Machine interpreter, a compiler with a C backend, and recently a VHDL backend to target FPGAs. Below is the Hume implementation of the client and calculator actors using boxes, courtesy of Gudmund Grov:

calculator.hume
data Cmd = Inc | Dec | Get; box calculator in (cmd :: Cmd,inp :: Int, state :: Int) out (outp :: Int,state' :: Int)
match (Inc,n,s) -> (*,n+s)
| (Dec,n,s) -> (*,s-n)
| (Get,*,s) -> (s,s); wire calculator (client.cmd,client.arg,calculator.state' initially 0) (output,calculator.state);
client.hume:
stream output to "std_out";
box client in (state :: Int) out (cmd :: Cmd, arg :: Int, state' :: Int)
match 0 -> (Inc,4,1)
| 1 -> (Inc,2,2)
| 2 -> (Dec,1,3)
| 3 -> (Get,*,*); wire client (client.state' initially 0) (calculator.cmd,calculator.inp,client.state); 

Actors in Erlang

Erlang is a general-purpose strict concurrent functional programming language and industry quality virtual machine. This implementation uses plain Erlang actors, and is simpler and shorter than the Erlang eXAT agent implementation later. The message passing calculator and client actors most closely honours the message sequence chart describing the specification:

box.erl
-module(box).
-export([start/0,calculator/1]). start() -> calculator(0). calculator(I) -> receive {inc,J} -> calculator(I+J); {dec,J} -> calculator(I-J); {get,Pid} -> Pid ! I end. 
client.erl
-module(client).
-export([client/0]). client() -> CalculatorPid = spawn(fun() -> box:start() end), CalculatorPid ! {inc,4}, CalculatorPid ! {inc,2}, CalculatorPid ! {dec,1}, CalculatorPid ! {get,self()}, receive I -> io:format("Answer: ~p~n",[I]) end.

Agents in eXAT

eXAT (A Stefano, 2009) is a FIPA-compliant agent programming platform to write and execute agents in Erlang OTP. Performatives INFORM and REQUEST are used to add context to messages, and the calculator examines the performative of each message it receives before evaluating the message content. The four messages in the implementation are: {INFORM, "inc,4"}, {INFORM, "inc,2"}, {INFORM, "dec,1"}, and {REQUEST, "get"}. eXAT also includes an Erlang based rule-processing engine, an agent execution environment for agent tasks based on finite-state machines, and support for transmitting and handling ACL messages. The source is on GitHub.

What is FIPA?

The FIPA standard is an agent oriented programming abstraction:

  • A standard for defining heterogeneous and interacting agents.
  • ACL is a widely adopted specification of the FIPA standard, based on speech act theory (J Searle, 1970).
  • The ACL specification defines a set of 22 performatives and their meaning.
FIPA.content: a thorn in the agent side

The FIPA ACL specification states that content of agent message is type restricted to String. In the Erlang implementation, sending messages to the calculator was straight forward, e.g. by sending {inc,2} to ask that it increments the calculator state by 2. The mandatory String type for FIPA messages rules out this solution. There are numerous approaches for dealing with this. The simplest is to use String tokenisation, e.g. to delimit a message "inc,2" from the client agent in to "inc" and "2", which is how the eXAT implementation below overcomes this limitation.

calculator.erl
-module(calculator).
-behaviour(agent). -export([start/0, stop/0]).
-export([code_change/3, handle_call/3,handle_acl/2, handle_cast/2, handle_info/2, init/2, terminate/2]). -include("acl.hrl").
-include("fipa_ontology.hrl"). %%API
start() -> agent:start_link(calculator, ?MODULE, []).
stop() -> agent:stop(calculator). %%agents callback
handle_acl(#aclmessage{speechact='REQUEST', sender=Sender}=Msg,State) -> spawn(fun () -> acl:reply(Msg, 'INFORM', State) end), {noreply, State}; handle_acl(#aclmessage{speechact = 'INFORM', sender=Sender}=Msg,State) -> Instruction = string:tokens(#aclmessage.content,","), Type = lists:nth(1,Instruction), Val = lists:nth(2,Instruction), case Type of "inc" -> NewState = State + Val; "dec" -> NewState = State - Val end, {noreply, NewState}. %% gen_server callbacks
init(Name, _Params) -> {ok, Name}.
handle_call(Call, _From, State) -> {reply, {error, unknown_call}, State}.
handle_cast(_Call, State) -> {noreply, State}.
handle_info(Msg, State) -> {noreply, State}.
code_change(_, State, _) -> {ok, State}.
terminate(_, _) -> ok. 
client.erl
-module(client).
-behaviour(agent). -export([start/0, stop/0]).
-export([code_change/3, handle_call/3,handle_acl/2, handle_cast/2, handle_info/2, init/2, terminate/2]). -include("acl.hrl").
-include("fipa_ontology.hrl"). %%API
start() -> agent:start_link(clientagent, ?MODULE, [{"localhost", 7778, <<"calculator">>}]).
stop() -> agent:stop(clientagent). %%agents callback
handle_acl(#aclmessage{speechact = 'INFORM'}=Msg, {_,DestAgent}=State) -> io:format("Answer: ~s~n", [#aclmessage.content]), {noreply, State}. %% gen_server callbacks
handle_info(ping, {SelfName, DestAgent} = State) -> {Ip, Port, Name} = DestAgent, Addr = list_to_binary(lists:flatten( io_lib:format("http://~s:~b",[Ip, Port]))), Dest = #'agent-identifier'{name = Name,addresses = [Addr]}, PingMsg = #aclmessage{sender = SelfName, receiver = Dest, content = <<"inc,4">>}, spawn(fun () -> Resp = acl:inform(PingMsg) end), PingMsg = #aclmessage{sender = SelfName, receiver = Dest, content = <<"inc,2">>}, spawn(fun () -> Resp = acl:inform(PingMsg) end), PingMsg = #aclmessage{sender = SelfName, receiver = Dest, content = <<"dec,1">>}, spawn(fun () -> Resp = acl:inform(PingMsg) end), {noreply, State}; init(Name, [DestAgent]) -> {ok, {Name, DestAgent}}.
handle_call(Call, _From, State) -> {reply, {error, unknown_call}, State}.
handle_cast(_Call, State) -> {noreply, State}.
handle_info(Msg, State) -> {noreply, State}.
code_change(_, State, _) -> {ok, State}.
terminate(_, _) -> ok.

Agents in Jade

The Jade agent framework (F Bellifemine et al, 1999) is another implementation of the FIPA specification. this time in Java. It is a port of the eXAT code. The reason for the increased code size is threefold:

  • Just as eXAT requires definitions of the gen_server interface functions, Jade users must adopt Jade conventions. That is, each agent must be instantiated and assigned behaviours, by overriding methods of the Agent and Behaviour superclasses.

  • Java is a verbose language. To save space I have omitted the ontology Java code, it totals 125 lines of Java. For a discussion on the verbosity of Jade agents vs eXAT, I would encourage the reader to inspect the language comparison in Figure 3 of (A Stefano, 2009).

  • The eXAT implementation uses simple String tokenising to parse instructions such as “increment 4”. The Jade framework has support for defining ontology’s with Java objects and Java reflection, to serialise and parse object-oriented ontology definitions to and from strings. The Jade calculator and client agents below share a custom Calculator ontology. As an example the “increment 4” instruction, when the getContentManager().fillContent(..) method is called is: ((action (agent-identifier :name calculator@my.laptop:1099/JADE) (CalculatorOperation :type 1 :value 4)))

Calculator.java
package com.box.calculator; import com.box.calculator.ops.*;
import static com.box.calculator.ops.CalculatorOperation.*;
import jade.content.*;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.*;
import jade.content.onto.basic.Action;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import static jade.lang.acl.ACLMessage.*;
import jade.lang.acl.MessageTemplate; public class Calculator extends Agent implements CalculatorVocabulary { Integer x; private final Ontology ontology = CalculatorOntology.getInstance(); private final Codec codec; public Calculator() { this.codec = new SLCodec(); } @Override protected void setup() { x = 0; getContentManager().registerLanguage(codec); getContentManager().registerOntology(ontology); addBehaviour(new CalculatorBehaviour()); } class CalculatorBehaviour extends CyclicBehaviour { @Override public void action() { MessageTemplate mt; mt = MessageTemplate.MatchOntology(ontology.getName()); ACLMessage msg = blockingReceive(mt); try { ContentElement content; content = getContentManager().extractContent(msg); Concept action = ((Action) content).getAction(); CalculatorOperation op = (CalculatorOperation) action; int performative = msg.getPerformative(); if (performative == INFORM) { switch (op.getType()) { case INC: x += op.getValue(); break; case DEC: x -= op.getValue(); break; default: System.out.println("Unexpected calculator operation"); } } else if (performative == REQUEST){ switch (op.getType()){ case GET: ACLMessage reply = msg.createReply(); reply.setPerformative(INFORM); reply.setContent(x.toString()); send(reply); break; default: System.out.println("Unexpected calculator operation"); } } } catch (Codec.CodecException e) { } catch (OntologyException e) { }
} } }
Client.java
package com.box.calculator; import com.box.calculator.ops.*;
import static com.box.calculator.ops.CalculatorOperation.*;
import jade.content.AgentAction;
import jade.content.lang.Codec;
import jade.content.lang.sl.SLCodec;
import jade.content.onto.*;
import jade.content.onto.basic.Action;
import jade.core.*;
import jade.core.behaviours.OneShotBehaviour;
import jade.lang.acl.ACLMessage; public class Client extends Agent { private final AID server = new AID("box", AID.ISLOCALNAME); private final Ontology ontology = CalculatorOntology.getInstance(); private final Codec codec = new SLCodec(); @Override protected void setup() { getContentManager().registerLanguage(codec); getContentManager().registerOntology(ontology); addBehaviour(new ClientBehaviour()); } class ClientBehaviour extends OneShotBehaviour { @Override public void action() { CalculatorOperation co = new CalculatorOperation(); co.setType(INC); co.setValue(4); sendMessage(ACLMessage.INFORM, co); co.setType(INC); co.setValue(2); sendMessage(ACLMessage.INFORM, co); co.setType(DEC); co.setValue(1); sendMessage(ACLMessage.INFORM, co); co.setType(GET); sendMessage(ACLMessage.REQUEST, co); ACLMessage reply = myAgent.blockingReceive(); System.out.println("Answer: " + reply.getContent()); } } void sendMessage(int performative, AgentAction action) { ACLMessage msg = new ACLMessage(performative); msg.setLanguage(codec.getName()); msg.setOntology(ontology.getName()); try { getContentManager().fillContent(msg, new Action(server, action)); msg.addReceiver(server); send(msg); } catch (Codec.CodecException ex) { } catch (OntologyException ex) { }
} } 

Project idea: A FIPA Multi-Agent System in CloudHaskell

CloudHaskell is a domain specific Erlang language embedded in Haskell, i.e. Erlang + monads + strong static typing. The original design is described in the Haskell in the Cloud paper, and is on hackage. A CloudHaskell Platform is being developed, which implements Erlang OTP abstractions on top of the CloudHaskell primitives.

The proposal is:

  • FIPA Haskell Implementation Implement the FIPA specification and agent framework with the CloudHaskell Platform. To get started, here is the FIPA specification document listing the 22 performatives and their meaning.

  • RDF As FIPA Message Content Standard The eXAT and Jade examples above demonstrate how to overcome the FIPA content String type restriction. Jade uses Java reflection to serialise and parse ontological information in ACL messages, but is not straight forward to use or debug for developers unfamiliar to reflection. Myself and others have proposed RDF as an alternative solution to encapsulate ontological meaning in Jade messages. RDF is a family of (W3C) specifications is used for conceptual description or modeling of information that is implemented in web resources. This idea could be borrowed in the CloudHaskell FIPA agent system, and the rdf4h Haskell library can be used for parsing and serialising RDF content between agents.

  • Interoperate with Jade & eXAT via MTP Implement the message transport protocol (MTP) with HTTP using one of the popular Haskell web servers, so that this agent framework can interoperate with Jade and eXAT via MTP.

Summary

This post compares four programming models: objects, boxes, actors and agents, by implementing a simple specification with a calculator unit and a client unit. Objected oriented programming with C++ and functional objects in Haskell are the simplest and shortest implementations. The Hume implementation defines the calculator and the client as finite state machines wired together, and again the code size is small. Actor oriented programming in Erlang is the simplest and shortest implementation with explicit message passing. Agent oriented programming is demonstrated twice, with the eXAT Erlang library and the Jade Java library. Both use FIPA performatives INFORM and REQUEST to help receiving agents predict the nature of the message. eXAT uses simple string tokenisation to serialise and parse calculation instructions. Jade uses a custom calculator ontology to structure message content passed to the calculator agent using Java reflection. But be warned:

Agent projects are often initialised with no clear goals in mind. With no goals, there are also no criteria for assessing the success or otherwise of the initiative, and no way of telling whether the project is going well or badly. The net result is that catastrophic project failure can occur seemingly out of the blue (M Wooldridge, 1998).

Without doubt, agent oriented modelling adds overhead to the programming effort. Programs are very often implementable with just plain Erlang actors, Java objects or Haskell etc. For those times where the FIPA standard is a good match, I propose the development of a FIPA platform on top of CloudHaskell Platform that uses the RDF W3C standard for message content, and includes an MTP HTTP component for interoperability with other FIPA platforms including eXAT and Jade. As a motivator:

Autonomous agents and multi-agent systems represent a new way of analysing, designing, and implementing complex software systems. The agent-based view offers a powerful repertoire of tools, techniques, and metaphors that have the potential to considerably improve the way in which people conceptualise and implement many types of software (N Jennings, 1998).