Custom Functions
View SourceYou can provide custom functions when evaluating expressions using the
functions: option.
iex> custom_functions = %{"double" => {1, fn [n], _context -> {:ok, n * 2} end}}
iex> Predicator.evaluate("double(score) > 100", %{"score" => 60}, functions: custom_functions)
{:ok, true}A function can read the evaluation context, not just its arguments:
iex> custom_functions = %{"user_role" => {0, fn [], context -> {:ok, Map.get(context, "current_user_role", "guest")} end}}
iex> Predicator.evaluate("user_role() == 'admin'", %{"current_user_role" => "admin"}, functions: custom_functions)
{:ok, true}A function that returns {:error, message} surfaces as an
EvaluationError, not a bare string - see the error shapes
reference:
iex> custom_functions = %{"divide" => {2, fn [a, b], _context ->
...> if b == 0, do: {:error, "Division by zero"}, else: {:ok, a / b}
...> end}}
iex> Predicator.evaluate("divide(10, 2) == 5", %{}, functions: custom_functions)
{:ok, true}
iex> custom_functions = %{"divide" => {2, fn [a, b], _context ->
...> if b == 0, do: {:error, "Division by zero"}, else: {:ok, a / b}
...> end}}
iex> {:error, err} = Predicator.evaluate("divide(10, 0)", %{}, functions: custom_functions)
iex> {err.__struct__, err.message}
{Predicator.Errors.EvaluationError, "Division by zero"}Overriding builtins
Custom functions are merged with the builtin set, and a custom function with the same name as a builtin overrides it for that evaluation only:
iex> override_functions = %{"len" => {1, fn [_], _context -> {:ok, "custom_result"} end}}
iex> Predicator.evaluate("len('anything')", %{}, functions: override_functions)
{:ok, "custom_result"}
iex> Predicator.evaluate("len('hello')", %{})
{:ok, 5}Function format
Custom functions must follow this format:
- Map key: function name (string)
- Map value:
{arity, function}tuple where:arity: the number of arguments the function expects, as an integer - or as a list of integers for a function with optional arguments (substring/2and/3both register under"substring" => {[2, 3], &call_substring/2}in the builtin string functions, for example)function: an anonymous function taking[args], contextand returning{:ok, result}or{:error, message}
Custom functions carry no global state - they are scoped to the single
evaluate/3 call that receives them, so concurrent evaluations with
different function sets never interfere with each other.