Rete.Taxonomy (Rete v0.2.0)

Copy Markdown View Source

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: an atom tag, or a module for a struct fact.

t()

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

alphas()

@type alphas() :: %{optional(fact_type()) => [term()]}

Alpha node ids, keyed by the fact type the condition is written against.

declaration()

@type declaration() ::
  {:derive, fact_type(), fact_type()} | {:underive, fact_type(), fact_type()}

An ordered taxonomy declaration, as returned by Rete.get_taxo_data/1.

fact_type()

@type fact_type() :: atom() | module()

A fact type: an atom tag, or a module for a struct fact.

t()

@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 folded Taxo hierarchy.
  • :fact_type_fn — a one-argument function that returns a fact's type.
  • :alphas — the condition type => alpha node ids map last indexed.
  • :index — the memoized fact type => alpha node ids map. It never holds an empty entry. A type absent from it propagates to nothing.

Functions

alpha_ids(taxonomy, fact)

@spec alpha_ids(t(), term()) :: [term()]

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.

alpha_ids_for_type(taxonomy, type)

@spec alpha_ids_for_type(t(), fact_type()) :: [term()]

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.

ancestors(taxonomy, type)

@spec ancestors(t(), fact_type()) :: [fact_type()]

The ancestors of type, sorted. [] for a type in no derivation.

default_fact_type(fact)

@spec default_fact_type(term()) :: fact_type()

The default :fact_type_fn.

  • a struct is typed by its module.
  • a tagged tuple {:type, ...}, of any arity, is typed by its first element.
  • a tagged map %{__type__: type} is typed by that value.

Anything else raises an error. If a fact had the wrong type by accident, it would match nothing, silently. You could not tell that case apart from a rule that simply 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(%Rete.IR.Test{})
Rete.IR.Test

expand(taxonomy, type)

@spec expand(t(), fact_type()) :: [fact_type()]

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]

fact_type(taxonomy, fact)

@spec fact_type(t(), term()) :: fact_type()

The type of fact, according to the taxonomy's :fact_type_fn.

from_modules(modules, opts \\ [])

@spec from_modules(
  [module()],
  keyword()
) :: t()

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.

index(taxonomy, alphas)

@spec index(t(), alphas()) :: t()

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.

is_a?(taxonomy, child, parent)

@spec is_a?(t(), fact_type(), fact_type()) :: boolean()

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}

new(taxo_data \\ [], opts \\ [])

@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 to default_fact_type/1.
  • :alphas — the condition type => alpha node ids map to index/2 right 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]