Function values, and the call of one (DMN 1.6, section 10.3.2.13, Table 64; D-82).
A function is a Feel.Value.Function of one of three kinds, and a call runs any of them the same
way: call/4 takes the value of the callee, the values of the arguments, and the span of the call.
:feel, a closure.closure/3builds it from the parameters, the type of the result, and a function of the environment and the values of the arguments that evaluates the body in the scope the function was written in. Both backends build it: the interpreter from a function that walks the body, and the generated code from a fun of its own.:native, a function of the host (Feel.Value.Function.native/2). Its arguments are converted to the boundary types it was built with, and its result back.:builtin, a built-in function, which a name stands for where nothing else has it (builtin/2, D-82). A call finds the function by its name and calls it as the call of the built-in does, by position or by name: the function takes the arguments as they are, and does not convert them to the types of its parameters. The one that a declared function type was given in place of a built-in with a signature for each kind (absfor afunction<number> -> number, D-197) is the exception: it has the one signature, and takes the arguments as a function of FEEL with those parameters does, as below.
The arguments
A call has its arguments by position, {:positional, values}, or by name, {:named, pairs}. By
position they are all supplied (Table 64): a number that is not the arity of the function is null
with an :arity_mismatch error. By name, the names have to be parameters of the function, and the ones
a call leaves out are null. An argument is then converted to the declared type of its parameter by
the implicit conversions of section 10.3.2.9.4 (Feel.Type.Conversion, reported as an :info, as
D-46 has them), and one that cannot be made to conform makes the whole call null with a
:type_mismatch error (Table 64: "the result of the invocation is null"). null conforms to every
type, and a parameter without a declared type is Any.
What is not a function
The callee of a call that is not a function value, null included, gives null with a
:not_a_function error (section 10.3.2.13.2).
Depth
A call is one level deeper than the call it is made in, and Feel.Limits has max_call_depth: a run
that passes it ends in either mode, with an error of kind :limit. A function can call itself only by
being passed to itself (a context entry cannot refer to its own key, D-58), and
{f: function(g) g(g), r: f(f)}.r would otherwise not end.
The number of calls
The depth does not bound how many calls a run makes (a function that calls itself twice at each level makes about
2ⁿ at a depth of n), so every call of a function of FEEL is counted too, and a run that passes max_calls ends
the same way (D-154). The calls that sort and list replace make of the function they are given are counted, as
their outcome carries the environment. A call of a built-in or of a host function runs no expression and is not
counted; a loop of them is bounded by max_iterations.
Summary
Types
The values of the arguments of a call: in the order of the parameters, or with the names they were given.
What a closure runs: the environment and the values of the arguments, one for each parameter.
Functions
The built-in function of this name as a value (D-82), with the signatures that the extensions of the run give it. The analyzer has seen that there is one.
Calls the value of a callee on the values of the arguments, and settles the outcome at span.
Builds the value of function(...) body: a closure with these parameters (names and declared types,
:any for a parameter without one), the type of what its body gives, and the function that runs the
body.
Builds the value of function(...) body written in the text that env is running (D-201): the closure of closure/3,
with the origin of env as its own. A call runs its body under that origin, whatever origin the text that calls it has,
so the diagnostics of the body say whose text their spans are in. Only the origin is kept of env, so the function
holds nothing of the run that made it.
Types
@type arguments() :: {:positional, [Feel.Value.t()]} | {:named, [{Feel.Name.t(), Feel.Value.t()}]}
The values of the arguments of a call: in the order of the parameters, or with the names they were given.
@type body() :: (Feel.Runtime.Env.t(), [Feel.Value.t()] -> {Feel.Value.t(), Feel.Runtime.Env.t()})
What a closure runs: the environment and the values of the arguments, one for each parameter.
Functions
@spec builtin(Feel.Runtime.Env.t(), Feel.Name.t()) :: Feel.Value.Function.t()
The built-in function of this name as a value (D-82), with the signatures that the extensions of the run give it. The analyzer has seen that there is one.
iex> env = Feel.Runtime.Env.new()
iex> function = Feel.Runtime.Invoke.builtin(env, "abs")
iex> {value, _env} = Feel.Runtime.Invoke.call(env, function, {:positional, [Decimal.new(-3)]}, {0, 3})
iex> Decimal.equal?(value, 3)
true
@spec call( Feel.Runtime.Env.t(), Feel.Value.t(), arguments(), Feel.Diagnostic.span() | nil ) :: {Feel.Value.t(), Feel.Runtime.Env.t()}
Calls the value of a callee on the values of the arguments, and settles the outcome at span.
iex> env = Feel.Runtime.Env.new()
iex> double = Feel.Runtime.Invoke.closure([{"a", :number}], :number, fn env, [a] ->
...> {:ok, twice} = Feel.Runtime.Arithmetic.multiply(a, Decimal.new(2))
...> {twice, env}
...> end)
iex> {value, _env} = Feel.Runtime.Invoke.call(env, double, {:positional, [Decimal.new(4)]}, {0, 4})
iex> Decimal.equal?(value, 8)
true
iex> {nil, env} = Feel.Runtime.Invoke.call(env, Decimal.new(1), {:positional, []}, {0, 3})
iex> Enum.map(Feel.Runtime.Env.diagnostics(env), & &1.code)
[:not_a_function]
@spec closure([{Feel.Name.t(), Feel.Type.t()}], Feel.Type.t(), body()) :: Feel.Value.Function.t()
Builds the value of function(...) body: a closure with these parameters (names and declared types,
:any for a parameter without one), the type of what its body gives, and the function that runs the
body.
iex> impl = fn env, [a] -> {a, env} end
iex> function = Feel.Runtime.Invoke.closure([{"a", :number}], :number, impl)
iex> {function.kind, Feel.Value.Function.type(function)}
{:feel, {:function, [:number], :number}}
@spec closure( [{Feel.Name.t(), Feel.Type.t()}], Feel.Type.t(), body(), Feel.Runtime.Env.t() ) :: Feel.Value.Function.t()
Builds the value of function(...) body written in the text that env is running (D-201): the closure of closure/3,
with the origin of env as its own. A call runs its body under that origin, whatever origin the text that calls it has,
so the diagnostics of the body say whose text their spans are in. Only the origin is kept of env, so the function
holds nothing of the run that made it.
iex> impl = fn env, [a] -> {a, env} end
iex> env = Feel.Runtime.Env.new(origin: :decision)
iex> function = Feel.Runtime.Invoke.closure([{"a", :number}], :number, impl, env)
iex> function.origin
{:origin, :decision}