The FEEL type function (DMN 1.6, section 10.3.2.8).
Functions are ordinary values: a decision model supplies business knowledge models and decision services as functions in the scope an expression runs in, and a host application can supply Elixir functions the same way. There are three kinds:
:feel- a function defined in FEEL withfunction(...). It closes over the scope where it was defined.:builtin- one of the built-in functions of the specification, which a name stands for where nothing else has it (D-82). It has the signatures of the function, and a call finds the function by its name. One that a declared function type was given in place of a function made of several signatures (narrowings/2, D-197) has one signature, and a call of it takes the arguments as a function of FEEL with those parameters does.:native- an Elixir function that the host application exposes. It is called with its arguments converted at the boundary to the Elixir types named in its signature.
The type of a function is function<T1, ..., Tn> -> R. Parameter names are not
part of it. A literal FEEL function's result descriptor comes from kind-only analysis in its
written environment and is preserved across analysis tiers, call specialization and optimization
(D-219). Its precise body call result is separate analysis information, not runtime metadata.
Signature inspection and callback checks use the descriptor; they do not inspect the implementation.
Treat the struct as opaque: build native functions with native/2 and read
them through the functions of this module. A built-in function has one type for each number of arguments it takes
(types_at/2), since a function type has a fixed number of parameters and date takes one argument or three.
Summary
Types
How a native function's argument or result is converted between FEEL and Elixir.
The Elixir function behind a native function.
A parameter: its name and its FEEL type.
A signature of a built-in function as a value needs it: the parameters (name, type, and whether a call may
leave it out), whether the last parameter stands for all the arguments from its place on, and the type of the
result. A type that the function takes in more than one kind is :any.
Functions
Returns the number of parameters of a function.
Builds a built-in function as a value, from its name and its signatures (Feel.Builtin.value/2 makes them from
the function). The parameters and the result of the first signature are those that params/1 and type/1 give.
variants are the signatures that the first ones were made of when they differ only in types (D-171): they are no
function type of the value, and say what a call of it does (refinements/1).
Returns the kind of a function.
Returns the name of a function, or nil when it has none.
The built-in function restricted to each signature it made one of (refinements/1) that takes arity arguments, each as a
function of its own (D-197): abs for a number, for a days and time duration, and for a years and months duration. One
of them is what a declared function type is given when the function itself is not an instance of it
(Feel.Type.Conversion).
Builds a native function from an Elixir function.
Returns the parameters of a function with their FEEL types.
The function types that say what a call of a built-in function does for part of what it takes (D-171): those of the signatures
that were made one for a call by name (Feel.Builtin.value/2), each as it was. abs is a function<Any> -> Any, and a call of
it with a number gives a number, as function<number> -> number says; it says nothing of a call with a string. They are not
function types of the value: abs instance of function<number> -> number is false, as the specification has it, and a name
declared function<number> -> number is not given abs but the function of the signature for a number (narrowings/2, D-197). None for a function that was not made of several signatures, and for a
function of FEEL or of the host.
Returns the FEEL function type of a function.
Returns the descriptor of function<T1, ..., Tn> -> R for the given parameter
types and result type.
All the function types of a function: one for a function of FEEL or of the host. For a built-in function, one
for each number of arguments that its signatures take, or :open if one of them is variadic and takes any number.
The function types that a function has for a number of arguments: [type(function)] for a function with as many
parameters, and none for another. A built-in function has one for each of its signatures that takes this many
arguments, the parameters of an optional parameter and of a variadic one being those of the arguments.
Returns true for a t/0.
A function of FEEL whose body reports its diagnostics with origin (D-201).
Types
@type boundary_type() ::
:feel | :number | :integer | :float | :string | :char | :boolean
How a native function's argument or result is converted between FEEL and Elixir.
| boundary type | FEEL side | Elixir side |
|---|---|---|
:feel | any value | the value; a context is a plain map (D-135) |
:number | number | Decimal.t/0 |
:integer | integral number | integer |
:float | number | float |
:string | string | binary |
:char | one-character string | code point |
:boolean | boolean | boolean |
The Elixir function behind a native function.
@type kind() :: :feel | :builtin | :native
@type param() :: {name :: String.t(), type :: Feel.Type.t()}
A parameter: its name and its FEEL type.
@type signature() :: %{ parameters: [{String.t(), Feel.Type.t(), boolean()}], variadic?: boolean(), returns: Feel.Type.t() }
A signature of a built-in function as a value needs it: the parameters (name, type, and whether a call may
leave it out), whether the last parameter stands for all the arguments from its place on, and the type of the
result. A type that the function takes in more than one kind is :any.
Functions
@spec arity(t()) :: non_neg_integer()
Returns the number of parameters of a function.
Builds a built-in function as a value, from its name and its signatures (Feel.Builtin.value/2 makes them from
the function). The parameters and the result of the first signature are those that params/1 and type/1 give.
variants are the signatures that the first ones were made of when they differ only in types (D-171): they are no
function type of the value, and say what a call of it does (refinements/1).
iex> signature = %{parameters: [{"n", :number, false}], variadic?: false, returns: :number}
iex> function = Feel.Value.Function.builtin("abs", [signature])
iex> {function.kind, Feel.Value.Function.type(function)}
{:builtin, {:function, [:number], :number}}
Returns the kind of a function.
Returns the name of a function, or nil when it has none.
@spec narrowings(t(), non_neg_integer()) :: [t()]
The built-in function restricted to each signature it made one of (refinements/1) that takes arity arguments, each as a
function of its own (D-197): abs for a number, for a days and time duration, and for a years and months duration. One
of them is what a declared function type is given when the function itself is not an instance of it
(Feel.Type.Conversion).
A restricted function has the one signature it was made of, with its parameter names and the types it declares for
this many arguments, and it takes only those: a call with another number of arguments, or with a value that is
not of the type of its parameter, is null with an error, as it is for a function of FEEL (Feel.Runtime.Invoke).
It is an instance of the function type of its signature and of no other, and has no refinement. None for a function that
is not a built-in, for one that was not made of several signatures, and for a variadic signature (a refinement is none
either).
iex> {:ok, abs} = Feel.Builtin.value("abs")
iex> [number | _] = Feel.Value.Function.narrowings(abs, 1)
iex> {number.name, Feel.Value.Function.params(number), Feel.Value.Function.types(number)}
{"abs", [{"n", :number}], [{:function, [:number], :number}]}
iex> Feel.Value.Function.refinements(number)
[]
iex> Feel.Value.Function.narrowings(abs, 2)
[]
Builds a native function from an Elixir function.
Options
:name- the function's name, for diagnostics.:params- a list of{name, boundary_type}pairs, one per parameter. Defaults to no parameters.:returns- the boundary type of the result. Defaults to:feel.
Raises ArgumentError for an unknown option or boundary type, or when
callable does not accept as many arguments as there are parameters.
iex> function = Feel.Value.Function.native(&max/2, name: "maximum", params: [{"a", :integer}, {"b", :integer}], returns: :integer)
iex> {Feel.Value.Function.arity(function), Feel.Value.Function.type(function)}
{2, {:function, [:number, :number], :number}}
Returns the parameters of a function with their FEEL types.
@spec refinements(t()) :: [Feel.Type.t()]
The function types that say what a call of a built-in function does for part of what it takes (D-171): those of the signatures
that were made one for a call by name (Feel.Builtin.value/2), each as it was. abs is a function<Any> -> Any, and a call of
it with a number gives a number, as function<number> -> number says; it says nothing of a call with a string. They are not
function types of the value: abs instance of function<number> -> number is false, as the specification has it, and a name
declared function<number> -> number is not given abs but the function of the signature for a number (narrowings/2, D-197). None for a function that was not made of several signatures, and for a
function of FEEL or of the host.
iex> {:ok, abs} = Feel.Builtin.value("abs")
iex> Feel.Value.Function.refinements(abs)
[
{:function, [:number], :number},
{:function, [:days_and_time_duration], :days_and_time_duration},
{:function, [:years_and_months_duration], :years_and_months_duration}
]
iex> {:ok, before} = Feel.Builtin.value("before")
iex> Feel.Value.Function.refinements(before)
[]
@spec type(t()) :: Feel.Type.t()
Returns the FEEL function type of a function.
A parameter without a declared type is :any, and so is a result without one.
@spec type([Feel.Type.t()], Feel.Type.t()) :: Feel.Type.t()
Returns the descriptor of function<T1, ..., Tn> -> R for the given parameter
types and result type.
@spec types(t()) :: [Feel.Type.t()] | :open
All the function types of a function: one for a function of FEEL or of the host. For a built-in function, one
for each number of arguments that its signatures take, or :open if one of them is variadic and takes any number.
iex> {:ok, length} = Feel.Builtin.value("string length")
iex> Feel.Value.Function.types(length)
[{:function, [:string], :number}]
iex> {:ok, sum} = Feel.Builtin.value("sum")
iex> Feel.Value.Function.types(sum)
:open
@spec types_at(t(), non_neg_integer()) :: [Feel.Type.t()]
The function types that a function has for a number of arguments: [type(function)] for a function with as many
parameters, and none for another. A built-in function has one for each of its signatures that takes this many
arguments, the parameters of an optional parameter and of a variadic one being those of the arguments.
iex> {:ok, date} = Feel.Builtin.value("date")
iex> Feel.Value.Function.types_at(date, 3)
[{:function, [:number, :number, :number], :date}]
iex> Feel.Value.Function.types_at(date, 2)
[]
Returns true for a t/0.
A function of FEEL whose body reports its diagnostics with origin (D-201).
The span of a diagnostic is a range of the text of the expression that made it. A function that one expression made and another
calls, as a host binds the business knowledge model that a decision calls, runs the text of the first inside the second, so
the diagnostics of its body have spans in a text that is not the one being run. With an origin they say whose text it is
(Feel.Diagnostic, :origin), and the host finds the span in it. The origin is whatever term the host likes.
A function that an expression writes has the origin of the run it is made in (the :origin option of Feel.run/3, nil
unless the host gave one, or the origin of the function whose body writes it), so a function passed to another and called
there, and one a function makes and gives back, report in their own text. This replaces that origin, for a function that
was made by a run that did not know whose text it is. A diagnostic of the call itself (the conversion of an argument, the
end of the call) is the caller's. A function that is not of FEEL runs no text, and is not changed.
iex> body = fn env, [_a] ->
...> {nil, Feel.Runtime.Env.report(env, Feel.Diagnostic.new(:invalid_property, "x", span: {0, 1}))}
...> end
iex> function = Feel.Runtime.Invoke.closure([{"a", :any}], :any, body) |> Feel.Value.Function.with_origin(:member)
iex> {nil, env} = Feel.Runtime.Invoke.call(Feel.Runtime.Env.new(), function, {:positional, [1]}, {0, 4})
iex> Enum.map(Feel.Runtime.Env.diagnostics(env), & &1.origin)
[:member]