Decides which alpha nodes a fact must be offered to.
Internal. A condition declares the fact type it is written against. But the alpha
expression it compiles to matches a fact of any shape. Type filtering happens here
instead, at propagation time. So derive/2 and underive/2 widen what a condition
sees, without recompiling an expression.
derive(:premium, :customer) reads "a premium is a customer". A :premium fact
reaches every condition written against :customer. A :customer fact must not
reach a condition written against :premium. So a fact of type t is offered to t
plus its ancestors, never its descendants.
iex> taxonomy =
...> Rete.Taxonomy.new([{:derive, :premium, :customer}],
...> alphas: %{customer: [:a1], premium: [:a2]}
...> )
iex> Rete.Taxonomy.alpha_ids(taxonomy, {:premium, 1})
[:a2, :a1]
iex> Rete.Taxonomy.alpha_ids(taxonomy, {:customer, 1})
[:a1]alpha_ids/2 runs for every inserted fact. So index/2 precomputes the whole
type => ids map. Empty entries are dropped. A type absent from the index answers
[], without allocating. This keeps a session that inserts foreign facts from
leaking. See docs/design/network.md §2.
Summary
Types
Alpha node ids, keyed by the fact type the condition is written against.
An ordered taxonomy declaration, as returned by Rete.get_taxo_data/1.
A fact type: any term except nil.
Fields
Functions
The ids of the alpha nodes fact must be offered to.
The ids of the alpha nodes a fact of type type must be offered to.
The ancestors of type, sorted. [] for a type in no derivation.
The default :fact_type_fn.
The condition types a fact of type type must be matched against.
The type of fact, according to the taxonomy's :fact_type_fn.
Builds a taxonomy from the derive/underive declarations of ruleset modules.
Precomputes the fact type => alpha node ids index for alphas.
Whether a fact of type child reaches a condition written against parent.
Builds a taxonomy from an ordered list of declarations.
Types
Alpha node ids, keyed by the fact type the condition is written against.
An ordered taxonomy declaration, as returned by Rete.get_taxo_data/1.
@type fact_type() :: term()
A fact type: any term except nil.
Taxo stores a type as a map key. This module indexes on it the same way. Neither needs
the type to be an atom.
nil is the one exception. The engine uses nil to mean that a fact declares no type.
See default_fact_type/1.
@type t() :: %Rete.Taxonomy{ alphas: alphas(), fact_type_fn: (term() -> fact_type()), index: %{optional(fact_type()) => [term()]}, taxo: %Taxo{ancestors: term(), descendants: term(), parents: term()} }
Fields:
:taxo— the foldedTaxohierarchy.:fact_type_fn— a one-argument function that returns a fact's type.:alphas— thecondition type => alpha node idsmap last indexed.:index— the memoizedfact type => alpha node idsmap. It never holds an empty entry. A type absent from it propagates to nothing.
Functions
The ids of the alpha nodes fact must be offered to.
Answers [] for a fact whose type no condition is written against, directly or through
a derivation.
The ids of the alpha nodes a fact of type type must be offered to.
See alpha_ids/2, which derives type from a fact.
The ancestors of type, sorted. [] for a type in no derivation.
The default :fact_type_fn.
A type is any term except nil, in every position below. Taxo and the alpha index
both store a type as a map key, so an atom is not required. "express", 42 and
{:tenant, 7} are all types, and derive/2 relates them like any other type.
These three rules apply in order:
- a
__type__key with a value other thannilgives the fact its type. This holds on a struct too. An explicit declaration takes precedence over the module. - otherwise, a struct takes its type from its module. A struct that declares a
__type__field but leaves it unset therefore lands here. - a tagged tuple
{type, ...}takes its type from its first element, at any arity.
nil is the one value that is not a type. The engine uses it to mean that a fact
declares no type. This is why an unset __type__ field on a struct falls through to the
module. A shape with nothing left to fall back on raises instead, and says so: both
%{__type__: nil} and {nil, 1} report that nil is not a fact type.
Every other value raises as well. A fact with an unexpected type would match nothing, and it would do so silently. You could not tell that case apart from a rule that does not apply.
iex> Rete.Taxonomy.default_fact_type({:order, 1, 99})
:order
iex> Rete.Taxonomy.default_fact_type(%{__type__: :order, id: 1})
:order
iex> Rete.Taxonomy.default_fact_type({"order", 1})
"order"
iex> Rete.Taxonomy.default_fact_type(%Rete.IR.Test{})
Rete.IR.Test
The condition types a fact of type type must be matched against.
type first, then its ancestors, sorted. The order does not depend on the order the
derivations were declared in.
iex> taxonomy = Rete.Taxonomy.new([{:derive, :dog, :mammal}, {:derive, :mammal, :animal}])
iex> Rete.Taxonomy.expand(taxonomy, :dog)
[:dog, :animal, :mammal]
iex> Rete.Taxonomy.expand(taxonomy, :rock)
[:rock]
The type of fact, according to the taxonomy's :fact_type_fn.
Builds a taxonomy from the derive/underive declarations of ruleset modules.
The declarations of all the modules are concatenated in module order, so a module can only undo a derivation declared by a module before it.
Precomputes the fact type => alpha node ids index for alphas.
alphas maps the type a condition is written against to the ids of the alpha
nodes built for it. The result answers alpha_ids/2 in one map lookup.
Whether a fact of type child reaches a condition written against parent.
True when the two are the same type, and when child derives from parent directly or
transitively.
iex> taxonomy = Rete.Taxonomy.new([{:derive, :dog, :mammal}])
iex> {Rete.Taxonomy.is_a?(taxonomy, :dog, :mammal), Rete.Taxonomy.is_a?(taxonomy, :mammal, :dog)}
{true, false}
@spec new( [declaration()], keyword() ) :: t()
Builds a taxonomy from an ordered list of declarations.
Options:
:fact_type_fn— a one-argument function that returns a fact's type. Defaults todefault_fact_type/1.:alphas— thecondition type => alpha node idsmap toindex/2right away. Defaults to%{}, which makes every lookup answer[].
Declarations are folded in order, so a later :underive undoes an earlier :derive.
This raises if a declaration is not a :derive or :underive tuple. It lets Taxo
raise on a cyclic derivation instead.
iex> taxonomy = Rete.Taxonomy.new([{:derive, :dog, :mammal}, {:derive, :mammal, :animal}])
iex> Rete.Taxonomy.ancestors(taxonomy, :dog)
[:animal, :mammal]