The type of the result of the call of a value, f(1), (function(a) a)(2), g(3)(4) (D-82), and what can be
said of the call before it runs.
The callee has a type, a union of what the value can be, and each alternative gives a result:
- a function
function<T1, ..., Tn> -> Rbounds a positional call byRwhen its parameter domains cover all arguments. Another arity or an uncovered argument domain givesAny | Null: the value can have unrecorded signatures. A call by name is not looked at for the number: the type of a function has the types of the parameters and not their names, so it cannot be known which ones the call supplies. A call with another number of arguments isAnyornull: a function type says that the function has an arrow within it and not that it has no other, and a built-in function bound to a name declaredfunction<number> -> number(ceiling, which takes a scale as well) has the arity (D-171). That the call isnullis known of a function written in the expression, which the analyzer finds out where the function is known (D-193), but not from an overload type alone; - an overload (D-171), a built-in function with several signatures, gives what the arrows that take this many arguments give,
made narrower by each refinement whose parameters hold the arguments (
Feel.Type.Arrows.application/2). An unrecorded arity or argument domain starts atAny | Null, since the recorded arrows need not be exhaustive; a named call of a function or overload gives
Any | Null: parameter names are absent from the type, and the names can select an unrecorded signature with a different result. Known function values are handled separately;Any, and a named type that is not looked into, givesAnyornull;nullgivesnull;- anything else (a number, a list, ...) is not a function, and gives
null.
When the function is known (a function written in the expression, called at once or through the key of a
context), the analyzer types the call from its arguments instead (D-193): bind/2 says which argument goes to
which parameter, and parameter_types/2 the type each parameter has in a body that is analyzed again.
Complete single-signature recursion peers also have separate evidence: a named call is bounded by the
written result or null, or only null if it supplies more names than the signature has parameters. This
does not prove that the names match; actual binding still checks unknown and duplicate names.
A call that can only be null for the last two reasons is warned about when the callee is not null or a name
that is unknown (which is reported as such): the warning says the callee is not a function, or that the number of
arguments is not the number of parameters. It is a warning and not an error, as for a built-in (D-56); the run
gives the null and reports the error.
Summary
Types
What is known of the arguments of a call: their types, by position, or by name.
What a call of the callee can certainly not do, when it is the same for all the callee can be.
Functions
The types of the arguments of a call in the order of the parameters of a function that has these (name, type)
parameters. null is the type of a parameter that a call by name leaves out. :error when the call does not fit:
another number of arguments, a name that is no parameter's, a name given twice.
The type of a call that was analyzed again for its arguments (D-193), specialized, once the function types it holds are put right.
The type a parameter has in the body when the call has an argument of this type for it, and whether every argument conforms to its parameter.
The type of the result of a call, and the problem that every function the callee can be has with it, if there is one.
Types
@type arguments() :: {:positional, [Feel.Type.Union.t()]} | {:named, [{Feel.Name.t(), Feel.Type.Union.t()}]}
What is known of the arguments of a call: their types, by position, or by name.
@type problem() :: {:not_a_function, [atom()]}
What a call of the callee can certainly not do, when it is the same for all the callee can be.
Functions
@spec bind([{Feel.Name.t(), Feel.Type.Union.t()}], arguments()) :: {:ok, [Feel.Type.Union.t()]} | :error
The types of the arguments of a call in the order of the parameters of a function that has these (name, type)
parameters. null is the type of a parameter that a call by name leaves out. :error when the call does not fit:
another number of arguments, a name that is no parameter's, a name given twice.
iex> parameters = [{"a", [:number, :null]}, {"b", [:string, :null]}]
iex> Feel.Analysis.Invocations.bind(parameters, {:positional, [[:number], [:string]]})
{:ok, [[:number], [:string]]}
iex> Feel.Analysis.Invocations.bind(parameters, {:named, [{"b", [:string]}]})
{:ok, [[:null], [:string]]}
iex> Feel.Analysis.Invocations.bind(parameters, {:positional, [[:number]]})
:error
iex> Feel.Analysis.Invocations.bind(parameters, {:named, [{"c", [:string]}]})
:error
@spec declared_closures(Feel.Type.Union.t(), Feel.Type.Union.t()) :: Feel.Type.Union.t()
The type of a call that was analyzed again for its arguments (D-193), specialized, once the function types it holds are put right.
The body of the function is analyzed again for the types of the arguments, and a function that the body writes (function(h) function(a) h gives one) is typed in that analysis: with h at the type of the argument. The function that the run holds is
the one that was written, with its parameters and its kind-only result signature, and that is what instance of and
a declared type ask about (Feel.Value.Function.types_at/2). So a function that the call gives and that the body writes is an
overload (D-171) of the arrow as it was written, which is what the algebra sees, with the arrow of this analysis as a
refinement, which types a call and nothing else. unspecialized is the type that the callee's own type gives the call, where the
body is as it was written. Equal arrow types do not prove that the call returns an argument unchanged: a new closure
can acquire that same type during specialization. Only a unique written arrow is paired; otherwise the unspecialized
function alternatives are retained. A literal body already carries its fixed arrow with the specialized
call refinement and keeps that overload intact. Arbitrary aliases and selection results still use the
conservative correspondence rules. This loses precision for identity functions and for every closure in a returned list,
range or context, whose result is widened to Any | Null below ({g: function(a) {h: function(c) a + 1}}.g(1).h(2) is
Any | Null, where a written signature that was known gave number | Null).
iex> written = {:function, [[:any, :null]], [:any, :null]}
iex> specialized = {:function, [[:any, :null]], [:number]}
iex> Feel.Analysis.Invocations.declared_closures([specialized], [written, :null])
[{:overload, [written], [specialized]}]
iex> Feel.Analysis.Invocations.declared_closures([specialized], [written])
[{:overload, [written], [specialized]}]
iex> Feel.Analysis.Invocations.declared_closures([:number], [written])
[:number]A function in a list or a context that the call gives is not paired with an arrow of the callee's type, which has no shape of
its container (D-193): it is typed by its parameters and Any | Null, the result that a function written without one has.
iex> specialized = {:function, [[:any, :null]], [:number]}
iex> container = {:context, %{"h" => [specialized]}}
iex> Feel.Analysis.Invocations.declared_closures([container], [:any, :null])
[{:context, %{"h" => [{:function, [[:any, :null]], [:any, :null]}]}}]
iex> Feel.Analysis.Invocations.declared_closures([{:list, []}], [:any, :null])
[{:list, []}]
@spec parameter_types([Feel.Type.Union.t()], [Feel.Type.Union.t()]) :: {[Feel.Type.Union.t()], boolean()}
The type a parameter has in the body when the call has an argument of this type for it, and whether every argument conforms to its parameter.
An argument that is within the declared type of its parameter is the value of the parameter as it is, so the
parameter has the type of the argument. One that may not be is converted to the declared type or the call is
null (Feel.Runtime.Invoke), so the body sees a value of the declared type, and the call may be null.
iex> declared = [[:number, :null], [:string, :null]]
iex> Feel.Analysis.Invocations.parameter_types(declared, [[:number], [:string]])
{[[:number], [:string]], true}
iex> Feel.Analysis.Invocations.parameter_types(declared, [[:number], [:number]])
{[[:number], [:string, :null]], false}
@spec result(Feel.Type.Union.t(), arguments()) :: {Feel.Type.Union.t(), problem() | nil}
The type of the result of a call, and the problem that every function the callee can be has with it, if there is one.
iex> function = [{:function, [[:number, :null]], [:number]}]
iex> Feel.Analysis.Invocations.result(function, {:positional, [[:number]]})
{[:number], nil}
iex> Feel.Analysis.Invocations.result(function, {:positional, [[:string]]})
{[:any, :null], nil}
iex> Feel.Analysis.Invocations.result(function, {:positional, []})
{[:any, :null], nil}
iex> overload = [{:overload, [{:function, [[:number, :null]], [:number]}], []}]
iex> Feel.Analysis.Invocations.result(overload, {:positional, []})
{[:any, :null], nil}
iex> Feel.Analysis.Invocations.result([:number], {:positional, []})
{[:null], {:not_a_function, [:number]}}