Observing what a session does, through one callback.
A listener is a module and a piece of state. The engine folds every event through it, and keeps the state it returns 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. The engine adds new event kinds as it grows. A listener that crashes on an unfamiliar event would turn every upgrade into a breaking change.
Events
| event | when |
|---|---|
{: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}}. It is a map, so a field can be
added later without changing the shape every listener matches on. {:propagated, ...}
is the exception, and it carries the bare id, because a join has no user-facing name.
origin is :asserted or {:derived, source}. This 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
@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.
@type origin() :: :asserted | {:derived, source()}
Where a fact came from.
Which terminal an event came from: its node id and its {module, name}.
@type state() :: term()
Anything a listener chooses to carry between events.