Behaviour for a module that supplies callable functions to the evaluator.
A provider names its functions by binding each name to an {arity, atom}
pair - the atom is the name of a def on the same module, with signature
(args :: [Types.value()], context) :: {:ok, Types.value()} | {:error, binary()}.
Naming the implementation by atom, rather than exposing a closure directly,
is what lets a caller resolve module and atom separately - apply/3
against the pair - instead of carrying a function() value around.
Predicator.Context.new/2's :providers option resolves a list of provider
modules into the evaluator's dispatch map; the four builtin modules
(SystemFunctions, DateFunctions, JSONFunctions, MathFunctions) are
the default provider list, named by builtin_providers/0.
Summary
Types
A function's binding: its accepted arity/arities, and the atom naming its implementation.
A function name, as it appears in a predicator expression.
Callbacks
Returns the functions this provider exposes, keyed by name.
Functions
The four builtin provider modules, in shadowing order.
Types
@type entry() :: {Predicator.Evaluator.function_arity(), atom()}
A function's binding: its accepted arity/arities, and the atom naming its implementation.
@type name() :: binary()
A function name, as it appears in a predicator expression.
Callbacks
Functions
@spec builtin_providers() :: [module()]
The four builtin provider modules, in shadowing order.
Predicator.Context.new/2 resolves this list first when builtins: true
(the default), so a later entry - a :providers module, then the inline
:functions map - overrides a same-named builtin. This is the one place
the default list is written down; conformance tooling that needs "every
registered name" calls Predicator.Context.new().functions rather than
duplicating it.