Illustration
An Actor implementation candidate in JavaScript.

The objective is to build a JavaScript implementation of an Actor as defined by Professor Carl Hewitt.

The following implementation is proposed:

class Actor { // State // Constructor // Interface // Behavior // Private } class Message { #address; #content; constructor(address, content = null) { this.#address = address; this.#content = content; } get address() { return this.#address; } get content() { return this.#content; } } async function send(message) { try { const actor = message.address; const result = await actor.behavior(message); if (result === undefined) { return 'ACK'; } else { return result; } } catch (error) { return error; } }

To illustrate the implementation, we model a client performing a deposit on its account.

const account = new Account(0); const client = new Client(100, account); client.deposit(60);
Logs

Implementation: GitHub.

  • Abstract definition of an Actor deduced from various publications of Professor Carl Hewitt and other sources.

An actor is built using const actor = new Actor(/* init state */), which introduces the actor actor initialized using values in /* init state */. An Actor may have a private state, so the Actor class starts with:

class Actor { /* * State * * The internal data of the Actor is known to itself only. */ #example_var; … }

An Actor is constructed by allocating memory, computation, and communication resources, including an address. This process is modeled by the class constructor.

class Actor { // State /* * Constructor * * A function that introduces a new actor and initializes its state. * */ constructor(/* init values */) { // State is initialized. this.#example_var = init_value; } … }

Using actor means sending messages to actor. Sending a message hello to actor is modeled by a method call:

class Actor { // State // Constructor /* * Interface * * Sends messages to the actor. * Replies may be waited for. */ hello() { … } }

hello may be implemented using synchronous or asynchronous code. A synchronous implementation may be:

hello() { return "world!"; }

In the asynchronous case, hello should send a message to actor, let the runtime execute whatever needs to be done and wait for an answer whenever it is available. Once received, the computation should continue. In this case, we need to define:

  • A Message.
  • A send(message) procedure that delivers the message to the target actor.
  • The actor behavior which defines how the actor will react to a given message.

A Message has an address and content. An address is an immutable piece of data that gives the ability to send a message to an actor.

class Hello { #address; #content; constructor(address, content = null) { this.#address = address; this.#content = content; } get address() { return this.#address; } get content() { return this.#content; } }

Then, the send operation is modeled using:

async function send(message) { try { const actor = message.address; const result = await actor.behavior(message); if (result === undefined) { return 'ACK'; } else { return result; } } catch (error) { return error; } }

which leads us to give an Actor a behavior:

class Actor { // State // Constructor // Interface /* * Behavior * * Defines how the actor reacts to messages. */ async behavior(message) { if (message instanceof Hello) { return "world"; } throw new Error(`Unexpected message ${message}.`); } }

The asynchronous version of hello may be implemented using:

async hello() { return await send(new Hello(this)); }

When waiting for a reply, the calling code may always use const world = await actor.hello(), which works for both the asynchronous and synchronous implementation of hello().

  • The JavaScript runtime is single-threaded by default.
  • It is not possible to map an actor to a Web Worker because they are too heavy; the number of Web Workers per browser is too limited to use them extensively.
  • Because of the points above — and others left implicit — the actor implementation proposed above is not faithful to the Actor definition.
  • Despite its limitations, we still get to use the semantics of actors.
  • A JavaScript implementation candidate has been given to illustrate how actors work.
  • This implementation is enough for simple programs.
  • Building this implementation shows the limitations of the JavaScript runtime and similar single-threaded runtimes.
  • The need for a special-purpose runtime like the one provided by the EVM is clear since it addresses this central question: how to map actors to isolated processes?