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
| 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}}, 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
@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.