Rete.Listener behaviour (Rete v0.1.0)

Copy Markdown View Source

Observing what a session does, through one callback.

A listener is a module and a piece of state. Every event is folded through it, and the state it returns is kept on the session, so a session with listeners is still an immutable value. An unobserved session costs nothing.

defmodule CountFirings do
  @behaviour Rete.Listener

  @impl true
  def handle_event({:activation_fired, _source, _token, _facts}, count), do: count + 1
  def handle_event(_event, count), do: count
end

session |> Rete.Session.with_listener(CountFirings, 0) |> Rete.Session.fire_rules()

A listener must have a catch-all clause. New event kinds are added as the engine grows, and one that crashes on an unfamiliar event would make upgrading a breaking change.

Events

eventwhen
{:fire_started, opts}fire_rules/2 begins
{:fire_finished, fired}the agenda is empty; fired is how many activations ran
{:fact_inserted, fact, origin}a fact is added to working memory
{:fact_retracted, fact, origin}a fact leaves working memory
{:fact_duplicated, fact}an equal fact was already present, so nothing propagated
{:propagated, op, node_id, count}a node consumed count items
{:activation_added, source, token}a production's LHS became satisfied
{:activation_removed, source, token}a pending activation was cancelled
{:activation_fired, source, token, facts}a rule ran and returned facts

source is %{node: node_id, rule: {module, name}}, a map so a field can be added without changing the shape every listener matches on. {:propagated, ...} is the exception and carries the bare id, because a join has no user-facing name. origin is :asserted or {:derived, source}, which is what lets a listener reconstruct provenance without reading memory. See docs/design/observability.md §1.

Summary

Types

An engine event. Match the ones you care about and ignore the rest.

Where a fact came from.

Which terminal an event came from: its node id and its {module, name}.

Anything a listener chooses to carry between events.

Callbacks

Handles one event, returning the listener's next state.

Types

event()

@type event() ::
  {:fire_started, keyword()}
  | {:fire_finished, non_neg_integer()}
  | {:fact_inserted, term(), origin()}
  | {:fact_retracted, term(), origin()}
  | {:fact_duplicated, term()}
  | {:propagated, atom(), term(), non_neg_integer()}
  | {:activation_added, source(), Rete.Token.t()}
  | {:activation_removed, source(), Rete.Token.t()}
  | {:activation_fired, source(), Rete.Token.t(), [term()]}

An engine event. Match the ones you care about and ignore the rest.

origin()

@type origin() :: :asserted | {:derived, source()}

Where a fact came from.

source()

@type source() :: %{node: term(), rule: {module(), atom()}}

Which terminal an event came from: its node id and its {module, name}.

state()

@type state() :: term()

Anything a listener chooses to carry between events.

Callbacks

handle_event(event, state)

@callback handle_event(event(), state()) :: state()

Handles one event, returning the listener's next state.

Implementations must include a catch-all clause.