comms v0.1.0 Comms.Actor behaviour

A behaviour module for implementing general purpose actors.

A Comms.Actor is designed to allow easy implementation of the actor model.

Example

defmodule Ping do
  use Comms.Actor

  @impl Comms.Actor
  def handle({:ping, pid}, state) do
    {[{pid, :pong}], state}
  end
end

{:ok, p} = Comms.Actor.start_link(Ping, nil)

send(p, {:ping, self()})

flush()
# => :pong

Actor Model

An actor is a computational entity that, in response to a message it receives, can concurrently:

  1. send a finite number of messages to other actors;
  2. create a finite number of new actors;
  3. designate the behavior to be used for the next message it receives.

There is no assumed sequence to the above actions and they could be carried out in parallel.

The Comms.Actor behavior is a replacement for GenServer that more closely follows these principles.

  • There is only one callback to deal with incomming messages, handle/2. Comms.Actor works with GenServer.call/2 etc but these are just another type of message
  • The return value of the handle/2 callback is always the same shape. A list of outbound messages and a new state. This allows an Comms.Agent to send any number of replies rather than 1 or 0 that is the case for GenServers.

Gen messages

Calls and cast from the GenServer module etc are just wrappers around the erlang :gen module. A proccess implementing Comms.Actor can respond to these like normal messages.

defmodule PairUp do
  use Comms.Actor

  def start_link do
    Comms.Actor.start_link(__MODULE__, :none)
  end

  @impl Comms.Actor
  def handle({:"$gen_call", from, {:pair, pid}}, :none) do
    {[], {:waiting, from, pid}}
  end
  def handle({:"$gen_call", from2, {:pair, pid2}}, {:waiting, from1, pid1}) do
    messages = [
      {from2, {:other, pid1}},
      {from1, {:other, pid2}},
    ]
    {messages, :none}
  end
end

Link to this section Summary

Types

Location where an actor can direct messages too

The payload of a sent or received message

Response to a

Any value that an actor maintains between receiving messages

Functions

Starts a Comms.Actor process without links (outside of a supervision tree)

Starts a Comms.Actor process linked to the current process

Link to this section Types

Link to this type address()
address() :: pid() | {pid(), reference()} | :timeout

Location where an actor can direct messages too.

Link to this type message()
message() :: term()

The payload of a sent or received message

Link to this type reaction()
reaction() :: {[{address(), message()}], state()}

Response to a

Link to this type state()
state() :: term()

Any value that an actor maintains between receiving messages

Link to this section Functions

Link to this function start(module, args, options \\ [])
start(module(), any(), GenServer.options()) :: GenServer.on_start()

Starts a Comms.Actor process without links (outside of a supervision tree).

See start_link/3 for more information.

Link to this function start_link(module, args, options \\ [])
start_link(module(), any(), GenServer.options()) :: GenServer.on_start()

Starts a Comms.Actor process linked to the current process.

The arguments for this are identical to GenServer.start_link/3 and should be used for reference docs.

Link to this section Callbacks

Link to this callback handle(message, state)
handle(message(), state()) :: reaction()