A built-in function of FEEL, as the registry describes it (D-56, which settles D-56).
The area modules under Feel.Builtin list their functions with functions/0, and
Feel.Builtin.function/1 finds one by name. A function has:
- a
name, canonical (seeFeel.Name). signatures: each a list of parameters (a name, the types it accepts, and whether it is optional) and the types that the result can be. The names let a call with named arguments be put in order (Feel.Analysis.Calls); the number of parameters, and how many are optional, decide how many arguments a call may have. The types are the description of the function and what the analyzer types a call by: whether an argument is one of them is decided by the function, when it runs.- a
result_fromof a signature, if the result depends on the types of the arguments and not only on the signature (D-151):max(xs)of alist<number>is a number andappend(xs, "a")is a list of numbers and strings, where the types of the result sayAnyandlist<Any>. It says how the type of the result is made from the types of the arguments (result_from/0), andFeel.Analysis.Calls.result_type/3makes it. - a
same_kindof a signature, if some of its arguments have to be of one kind that has an order and not only each of one that its parameter takes (D-155): the two points ofbefore(point1, point2), the items ofmax(c1, c2). A parameter says what one argument may be, and this says that the arguments agree, whichFeel.Analysis.Callschecks (same_kind/0). call: the function itself. It takes the environment of the run (Feel.Runtime.Env, for the clock thatnow()reads) and the list of its arguments, already evaluated and in order, and returns an outcome (Feel.Runtime.Env.outcome/0):{:ok, value},:missingfor anullthat is not a mistake, or{:error, diagnostic}for anullthat is. It is called only with a number of arguments that some signature accepts.pure?: whether the result depends on the arguments alone. A call of a pure function with constants is folded by the analyzer and the optimizer;now()andtoday()read the clock, so they are not.never_null?: whether the result is nevernullfor an argument of a signature, as the result ofis. The analyzer types the result of any other function with anull.check_arguments?: whether an argument that no signature takes isnullwith an:argument_out_of_domainerror before the function is called, which the analyzer knows (Feel.Analysis.Calls.mismatch/3) and a call by name checks against its signature (D-56). It isfalsefor a function whose own error for a wrong kind is another, asnot, whose error is a type mismatch like the operators': the function says it, and what it says does not change.
Summary
Types
A number of arguments that a function takes: exactly that many, or that many and more (the arguments of a variadic parameter).
What a function returns. An overload of an extension (with_overloads/2) may also say :next: the
arguments are not for it. :next does not leave Feel.Builtin.call/4: the overloads are tried by with_overloads/2, and
a function that is not an overload and says it has the arguments refused, as an error of their domain.
What an argument that has to be of one kind may be of (same_kind/0): {:kinds, kinds} the kinds that have an order
that it can be of, which is none for a value that has no order, or :free if it is of no kind in particular (it may be
null, or anything). The analyzer says it of the type of an argument, and the run of its value.
How the type of a result is made from the types of the arguments (D-151), by the places of the arguments (from 0; a place at or after a variadic parameter is that argument and all that follow)
The arguments of a call that have to be of one kind that has an order (D-155), by the places of the arguments (from 0; a
place at or after a variadic parameter is that argument and all that follow): a number, a string, a date, a time, a
date and time, or a duration of one of the two kinds. The call is out of the domain when the kinds that the arguments
may be of have nothing in common, as before("a", 2) is. A part is
A signature. One that an extension adds (Feel.Extension, D-81) has extension: true.
Functions
Returns true when a call with this many arguments is one that some signature accepts.
Whether the arguments, as the kinds they may be of, agree: some kind that has an order is the kind of every one of
them that is not :free. A single argument that cannot be of such a kind (it has no order) does not agree with
itself, and no argument that is not free is one that agrees.
The arguments (or the values) that are at a place of a signature: the one at the place, and for a variadic parameter all that follow. A parameter that the call leaves out has none.
The numbers of arguments that a call may have: for each signature, from its required parameters to
all of them. A signature with a variadic parameter takes {:at_least, n}, and that covers the numbers
from n of the others.
The type of the elements of a type of a parameter that is a list.
The signatures of extensions that have a parameter of each of the names (D-81). A call by name is for one signature,
the one for_names/2 chooses, but an extension may take the call all the same, with the same names for other kinds of
argument; a call that one fits is the extension's to answer.
The signature that a call with named arguments is for: the first one that has a parameter of each
of the names. The call is then put in the order of its parameters, and a parameter that the call
leaves out is null (see Feel.Analysis.Calls).
Returns the function with the signatures that take each number of arguments, up to 12, kept in it (D-56): the answer of
accepts?/2 and with_arity/2 is then a lookup and not a pass over the signatures, each of which works out its numbers
of arguments from its parameters. Feel.Builtin.function/1 does this once for the table it keeps. A function whose
signatures are changed has to lose it (with_overloads/2 does), since the index is of the signatures it was made from.
The signatures with those that differ only in types made one: the same parameters (the names, and whether each is optional, variadic, quiet or an integer) taking the types of all of them, and the same result with the types of all.
The kinds of value that have an order (Table 55), which is what a same_kind is about: a number, a string, a date, a
time, a date and time, and a duration of either kind. A boolean, a list, a context, a range and a function have none.
A parameter: its name, the types it accepts, and optional: true if a call may leave it out.
The parameter that the argument at this place (from 0) is for: the one at that place, or for an argument past the
last, the variadic parameter that ends the signature; nil if there is none.
Whether the argument at this place (from 0) is read quietly: every one of the signatures has a quiet parameter there.
The signature with the arguments that have to be of one kind (same_kind/0, D-155). nil says there are none.
A signature: its parameters and the types of its result, and how the result follows the types of the arguments if it does
(result_from/0).
The kind that a type of a parameter stands for: a primitive type is itself, and a list, a range, a context and
a function are :list, :range, :context and :function, whatever they are of. :any takes every kind.
Returns true for a variadic parameter.
The signatures that a signature from for_names/2 stands for: those of the function that differ from it only in types
(merge_variants/1), or itself if it is one of an extension. A call by name is typed by each of them.
The signatures that accept this many arguments.
The function with the overloads of extensions added to it (Feel.Extension, D-81): its signatures are those
of the function and then those of the overloads, in the order of the extensions.
Types
@type argument_count() :: non_neg_integer() | {:at_least, non_neg_integer()}
A number of arguments that a function takes: exactly that many, or that many and more (the arguments of a variadic parameter).
@type call_outcome() :: Feel.Runtime.Env.outcome() | :next
What a function returns. An overload of an extension (with_overloads/2) may also say :next: the
arguments are not for it. :next does not leave Feel.Builtin.call/4: the overloads are tried by with_overloads/2, and
a function that is not an overload and says it has the arguments refused, as an error of their domain.
@type kind_slot() :: {:kinds, [atom()]} | :free
What an argument that has to be of one kind may be of (same_kind/0): {:kinds, kinds} the kinds that have an order
that it can be of, which is none for a value that has no order, or :free if it is of no kind in particular (it may be
null, or anything). The analyzer says it of the type of an argument, and the run of its value.
@type parameter() :: %{ :name => String.t(), :types => [Feel.Type.t(), ...], :optional? => boolean(), optional(:variadic?) => true, optional(:quiet?) => true, optional(:integer?) => true }
@type result_from() :: {:list_of, [{:elements | :flat | :items, non_neg_integer()}, ...]} | {:list_of, [{:elements | :flat | :items, non_neg_integer()}, ...], [{:elements | :items, non_neg_integer()}]} | {:list_of, [{:elements | :flat | :items, non_neg_integer()}, ...], [{:elements | :items, non_neg_integer()}], :always | [{:elements, non_neg_integer()}]} | {:element_of, non_neg_integer()} | {:item_of, non_neg_integer()}
How the type of a result is made from the types of the arguments (D-151), by the places of the arguments (from 0; a place at or after a variadic parameter is that argument and all that follow):
{:list_of, parts}and{:list_of, parts, non_empty_from}: a list of the elements that the parts give:{:elements, place}the elements of a list (a value that is not a list is the list of it, section 10.3.2.9.4),{:flat, place}the same with the lists inside them taken apart asflattendoes, and{:items, place}the arguments themselves, as the items they are.non_empty_fromof a:list_of(D-152) lists the places that make the result a list that has an element:{:elements, place}when the argument there has one (a list that has an element, or a value, which is the list of itself),{:items, place}when an item is given there.- a fourth element of a
:list_ofsays when the call isnull(D-152)::always(a position out of range, a function that is not one, ...) or the places{:elements, place}whose argument beingnullmakes it so, for a function that fails for nothing else (reverse,append, ...); the result is then notnullwhen none of those may be. {:element_of, place}: one of the elements of the list, ornullif there is none (max,min); notnullwhen the list has an element and they are of one kind that is ordered.{:item_of, place}: one of the arguments (max(a, b, c)).
@type same_kind() :: [{:point | :item_of | :elements, non_neg_integer()}, ...]
The arguments of a call that have to be of one kind that has an order (D-155), by the places of the arguments (from 0; a
place at or after a variadic parameter is that argument and all that follow): a number, a string, a date, a time, a
date and time, or a duration of one of the two kinds. The call is out of the domain when the kinds that the arguments
may be of have nothing in common, as before("a", 2) is. A part is:
{:point, place}: the argument at the place, as a point, which is the value itself or the list of one element that is it (section 10.3.2.9.4):before(point1, point2).{:item_of, place}: the arguments from the place on, as the items ofmax(c1, ..., cn): each is an item, except for one argument alone, which is the list of items if it is a list (as forresult_from).{:elements, place}: the elements of the list at the place, which are the items ofmax(list).
An argument that is null, of any type, or of the name of a model type, is of no kind in particular and agrees with all.
@type signature() :: %{ :parameters => [parameter()], :result => [Feel.Type.t(), ...], optional(:result_from) => result_from(), optional(:same_kind) => same_kind(), optional(:extension) => true }
A signature. One that an extension adds (Feel.Extension, D-81) has extension: true.
@type t() :: %Feel.Builtin.Function{ arity_index: %{required(non_neg_integer()) => [signature()]} | nil, call: (Feel.Runtime.Env.t(), [Feel.Value.t()] -> call_outcome()), check_arguments?: boolean(), name: Feel.Name.t(), never_null?: boolean(), pure?: boolean(), signatures: [signature(), ...] }
Functions
@spec accepts?(t(), non_neg_integer()) :: boolean()
Returns true when a call with this many arguments is one that some signature accepts.
Whether the arguments, as the kinds they may be of, agree: some kind that has an order is the kind of every one of
them that is not :free. A single argument that cannot be of such a kind (it has no order) does not agree with
itself, and no argument that is not free is one that agrees.
iex> Feel.Builtin.Function.agree?([{:kinds, [:number, :string]}, {:kinds, [:string]}, :free])
true
iex> Feel.Builtin.Function.agree?([{:kinds, [:number]}, {:kinds, [:string]}])
false
iex> Feel.Builtin.Function.agree?([{:kinds, [:number, :string]}, {:kinds, [:number]}, {:kinds, [:string]}])
false
iex> Feel.Builtin.Function.agree?([{:kinds, []}])
false
iex> Feel.Builtin.Function.agree?([:free, :free])
true
@spec arguments_at(signature(), [argument], non_neg_integer()) :: [argument] when argument: var
The arguments (or the values) that are at a place of a signature: the one at the place, and for a variadic parameter all that follow. A parameter that the call leaves out has none.
iex> {:ok, sum} = Feel.Builtin.function("sum")
iex> [list, items] = sum.signatures
iex> Feel.Builtin.Function.arguments_at(list, [1, 2, 3], 0)
[1]
iex> Feel.Builtin.Function.arguments_at(items, [1, 2, 3], 0)
[1, 2, 3]
iex> Feel.Builtin.Function.arguments_at(list, [1], 3)
[]
@spec arities(t()) :: [argument_count()]
The numbers of arguments that a call may have: for each signature, from its required parameters to
all of them. A signature with a variadic parameter takes {:at_least, n}, and that covers the numbers
from n of the others.
iex> {:ok, time} = Feel.Builtin.function("time")
iex> Feel.Builtin.Function.arities(time)
[1, 3, 4]
@spec element_spec(Feel.Type.t()) :: Feel.Type.t()
The type of the elements of a type of a parameter that is a list.
iex> Feel.Builtin.Function.element_spec({:list, :string})
:string
The signatures of extensions that have a parameter of each of the names (D-81). A call by name is for one signature,
the one for_names/2 chooses, but an extension may take the call all the same, with the same names for other kinds of
argument; a call that one fits is the extension's to answer.
iex> {:ok, date} = Feel.Builtin.function("date")
iex> Feel.Builtin.Function.extensions_for_names(date, ["year", "month", "day"])
[]
The signature that a call with named arguments is for: the first one that has a parameter of each
of the names. The call is then put in the order of its parameters, and a parameter that the call
leaves out is null (see Feel.Analysis.Calls).
Names have no order in a call, but two signatures can have the same names in different orders, as
before(point, range) and before(range, point) do, and what the function says differs. Of the
signatures that fit, the first that has the parameters in the order in which the names are written is
the one; if there is none, the first that fits.
A signature that has exactly the names of the call goes before one that has more (D-81): the overload
date and time(date, timezone) of an extension is the one for date: ..., timezone: ..., and not the
signature (date, time, timezone) of DMN 1.6 with the time left out. This is only a choice among the
signatures that fit, and it is the same as before where none has exactly the names.
Signatures that differ only in types are one signature here (merge_variants/1): the names do not say of what kind
the arguments are.
iex> {:ok, time} = Feel.Builtin.function("time")
iex> {:ok, signature} = Feel.Builtin.Function.for_names(time, ["hour", "minute", "second"])
iex> Enum.map(signature.parameters, & &1.name)
["hour", "minute", "second", "offset"]
iex> Feel.Builtin.Function.for_names(time, ["hour", "hours"])
:error
iex> {:ok, before} = Feel.Builtin.function("before")
iex> {:ok, signature} = Feel.Builtin.Function.for_names(before, ["range", "point"])
iex> Enum.map(signature.parameters, & &1.name)
["range", "point"]
Returns the function with the signatures that take each number of arguments, up to 12, kept in it (D-56): the answer of
accepts?/2 and with_arity/2 is then a lookup and not a pass over the signatures, each of which works out its numbers
of arguments from its parameters. Feel.Builtin.function/1 does this once for the table it keeps. A function whose
signatures are changed has to lose it (with_overloads/2 does), since the index is of the signatures it was made from.
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> abs.arity_index |> Map.fetch!(1) |> length()
3
iex> abs.arity_index |> Map.fetch!(2)
[]
The signatures with those that differ only in types made one: the same parameters (the names, and whether each is optional, variadic, quiet or an integer) taking the types of all of them, and the same result with the types of all.
A call by name sees a function so. Its names say which parameters the call has and not of what kinds they are, so
abs(n: x) is for the three kinds that abs takes, and not for the first signature that has an n. The signatures of
an extension are never made one (D-81), and a signature that differs from every other in its parameters is as it was.
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> [merged] = Feel.Builtin.Function.merge_variants(abs.signatures)
iex> Enum.map(merged.parameters, & &1.types)
[[:number, :days_and_time_duration, :years_and_months_duration]]
iex> merged.result
[:number, :days_and_time_duration, :years_and_months_duration]
iex> {:ok, date} = Feel.Builtin.function("date")
iex> Feel.Builtin.Function.merge_variants(date.signatures) == date.signatures
true
@spec ordered_kinds() :: [atom(), ...]
The kinds of value that have an order (Table 55), which is what a same_kind is about: a number, a string, a date, a
time, a date and time, and a duration of either kind. A boolean, a list, a context, a range and a function have none.
iex> :date in Feel.Builtin.Function.ordered_kinds()
true
iex> :boolean in Feel.Builtin.Function.ordered_kinds()
false
@spec parameter(String.t(), [Feel.Type.t(), ...], keyword()) :: parameter()
A parameter: its name, the types it accepts, and optional: true if a call may leave it out.
A parameter that is variadic: true stands for all the arguments from its place on, so it is the last
of its signature (append(list, item...), sum(n1...)). It takes one argument at least, and none if it
is also optional: true. The map has the key :variadic? only for such a parameter.
A parameter that is quiet: true is read without reporting (D-100): what is wrong in the argument, a name that is not
bound, a path through an entry that is not there, is not reported, neither when the expression is prepared nor when it
is run, and does not end a mode: :strict run. The argument is null then, as it is without the flag. The map has the
key :quiet? only for such a parameter.
A parameter that is integer: true wants an integer: a number with a fractional part is cut to one before the function
looks at it ("from decimal to integer", DMN 1.6 section 10.3.2.9.4, Feel.Builtin.Arguments.integer/1), and the run
reports that (D-46). The map has the key :integer? only for such a parameter.
iex> Feel.Builtin.Function.parameter("offset", [:days_and_time_duration, :null], optional: true)
%{name: "offset", types: [:days_and_time_duration, :null], optional?: true}
iex> Feel.Builtin.Function.parameter("item", [:any], optional: true, variadic: true)
%{name: "item", types: [:any], optional?: true, variadic?: true}
iex> Feel.Builtin.Function.parameter("value", [:any], quiet: true)
%{name: "value", types: [:any], optional?: false, quiet?: true}
iex> Feel.Builtin.Function.parameter("scale", [:number], integer: true)
%{name: "scale", types: [:number], optional?: false, integer?: true}
@spec parameter_at(signature(), non_neg_integer()) :: parameter() | nil
The parameter that the argument at this place (from 0) is for: the one at that place, or for an argument past the
last, the variadic parameter that ends the signature; nil if there is none.
iex> {:ok, sum} = Feel.Builtin.function("sum")
iex> [_list, variadic] = sum.signatures
iex> Feel.Builtin.Function.parameter_at(variadic, 5).name
"n1"
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> Feel.Builtin.Function.parameter_at(hd(abs.signatures), 1)
nil
@spec quiet_at?([signature()], non_neg_integer()) :: boolean()
Whether the argument at this place (from 0) is read quietly: every one of the signatures has a quiet parameter there.
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> Feel.Builtin.Function.quiet_at?(abs.signatures, 0)
false
iex> Feel.Builtin.Function.quiet_at?([], 0)
false
The signature with the arguments that have to be of one kind (same_kind/0, D-155). nil says there are none.
iex> points = [Feel.Builtin.Function.parameter("point1", [:number, :string]), Feel.Builtin.Function.parameter("point2", [:number, :string])]
iex> signature = Feel.Builtin.Function.signature(points, [:boolean])
iex> Feel.Builtin.Function.same_kind(signature, [{:point, 0}, {:point, 1}]).same_kind
[point: 0, point: 1]
iex> Feel.Builtin.Function.same_kind(signature, nil) == signature
true
@spec signature([parameter()], [Feel.Type.t(), ...], result_from() | nil) :: signature()
A signature: its parameters and the types of its result, and how the result follows the types of the arguments if it does
(result_from/0).
iex> param = Feel.Builtin.Function.parameter("from", [:string])
iex> Feel.Builtin.Function.signature([param], [:date])
%{parameters: [%{name: "from", types: [:string], optional?: false}], result: [:date]}
iex> list = Feel.Builtin.Function.parameter("list", [{:list, :any}])
iex> Feel.Builtin.Function.signature([list], [{:list, :any}], {:list_of, [{:elements, 0}]}).result_from
{:list_of, [{:elements, 0}]}
@spec spec_kind(Feel.Type.t()) :: atom()
The kind that a type of a parameter stands for: a primitive type is itself, and a list, a range, a context and
a function are :list, :range, :context and :function, whatever they are of. :any takes every kind.
iex> Feel.Builtin.Function.spec_kind({:list, :string})
:list
iex> Feel.Builtin.Function.spec_kind(:number)
:number
Returns true for a variadic parameter.
The signatures that a signature from for_names/2 stands for: those of the function that differ from it only in types
(merge_variants/1), or itself if it is one of an extension. A call by name is typed by each of them.
iex> {:ok, abs} = Feel.Builtin.function("abs")
iex> {:ok, signature} = Feel.Builtin.Function.for_names(abs, ["n"])
iex> length(Feel.Builtin.Function.variants_of(abs, signature))
3
@spec with_arity(t(), non_neg_integer()) :: [signature()]
The signatures that accept this many arguments.
iex> {:ok, date} = Feel.Builtin.function("date")
iex> [signature] = Feel.Builtin.Function.with_arity(date, 3)
iex> Enum.map(signature.parameters, & &1.name)
["year", "month", "day"]
The function with the overloads of extensions added to it (Feel.Extension, D-81): its signatures are those
of the function and then those of the overloads, in the order of the extensions.
The function runs first. If it reports an error, or if none of its signatures takes the number of
arguments, the overloads that take it are tried, in order, and the first that does not answer :next is the
result. If none does, the result is the error of the function, which is the one it would give without
the overloads (Feel.Builtin.arity_mismatch/2 when its signatures did not fit). A null that the function
gives because an argument is null (:missing) is a result, and the overloads do not see it. So what was a
result before is the result still.
The function is as pure as all of them are, and as free of null as all of them are.
iex> {:ok, number} = Feel.Builtin.function("number")
iex> overload = %Feel.Builtin.Function{
...> name: "number",
...> signatures: [
...> Feel.Builtin.Function.signature(
...> [Feel.Builtin.Function.parameter("from", [:string]), Feel.Builtin.Function.parameter("group", [:string])],
...> [:number]
...> )
...> ],
...> call: fn _env, [_from, _group] -> {:ok, Decimal.new(2)} end
...> }
iex> merged = Feel.Builtin.Function.with_overloads(number, [overload])
iex> Feel.Builtin.Function.arities(number)
[1, 3]
iex> Feel.Builtin.Function.arities(merged)
[1, 2, 3]
iex> merged.call.(Feel.Runtime.Env.new(), ["1", ","])
{:ok, Decimal.new(2)}
iex> merged.call.(Feel.Runtime.Env.new(), ["1.5"])
{:ok, Decimal.new("1.5")}