Type.Function (mavis v0.0.6) View Source
Represents a function type.
There are two fields for the struct defined by this module.
paramsa list of types for the function arguments. Note that the arity of the function is the length of this list. May also be the atom:anywhich corresponds to "a function of any arity".returnthe type of the returned value.
Examples:
(... -> integer())would be represented as%Type.Function{params: :any, return: %Type{name: :integer}}(integer() -> integer())would be represented as%Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}}
Shortcut Form
The Type module lets you specify a function using "shortcut form" via the Type.function/1 macro:
iex> import Type, only: :macros
iex> function((atom() -> pos_integer()))
%Type.Function{params: [%Type{name: :atom}], return: %Type{name: :pos_integer}}Inference
By default, Mavis will not attempt to perform inference on function types.
iex> inspect Type.of(&(&1 + 1))
"(any() -> any())"If you would like to perform inference on the function to obtain
more details on the acceptable function types, set the inference
environment variable. For example, if you're using the :mavis_inference hex package, do:
Application.put_env(:mavis, :inference, Type.Inference)The default module for this is Type.NoInference
Key functions:
comparison
Functions are ordered first by the type order on their return type, followed by type order on their parameters.
iex> import Type, only: :macros
iex> Type.compare(function(( -> atom())), function(( -> integer())))
:gt
iex> Type.compare(function((integer() -> integer())),
...> function((atom() -> integer())))
:ltintersection
Functions with distinct parameter types are nonoverlapping, even if their parameter types overlap. If they have the same parameters, then their return values are intersected.
iex> import Type, only: :macros
iex> Type.intersection(function(( -> 1..10)), function(( -> integer())))
%Type.Function{params: [], return: 1..10}
iex> Type.intersection(function((integer() -> integer())),
...> function((1..10 -> integer())))
%Type{name: :none}functions with :any parameters intersected with a function with specified parameters
will adopt the parameters of the intersected function.
iex> import Type, only: :macros
iex> Type.intersection(function((... -> pos_integer())),
...> function((1..10 -> pos_integer())))
%Type.Function{params: [1..10], return: %Type{name: :pos_integer}}union
Functions are generally not merged in union operations, but if their parameters are identical then their return types will be merged.
iex> import Type, only: :macros
iex> Type.union(function(( -> 1..10)), function(( -> 11..20)))
%Type.Function{params: [], return: 1..20}subtype?
A function type is the subtype of another if it has the same parameters and its return value type is the subtype of the other's
iex> import Type, only: :macros
iex> Type.subtype?(function((integer() -> 1..10)),
...> function((integer() -> integer())))
trueusable_as
The usable_as relationship for functions may not necessarily be obvious. An
easy way to think about it, is: if I passed a function with this type to a
function that demanded the other type how confident would I be that it would
not crash.
A function is usable_as another function if all of its parameters are
supertypes of the targeted function; and if its return type is subtypes of the
return type of the targeted function.
iex> import Type, only: :macros
iex> Type.usable_as(function((pos_integer() -> 1..10)), function((1..10 -> pos_integer())))
:ok
iex> Type.usable_as(function((1..10 -> 1..10)), function((pos_integer() -> pos_integer())))
{:maybe, [%Type.Message{type: %Type.Function{params: [1..10], return: 1..10},
target: %Type.Function{params: [%Type{name: :pos_integer}], return: %Type{name: :pos_integer}}}]}
iex> Type.usable_as(function(( -> atom())), function(( -> pos_integer())))
{:error, %Type.Message{type: %Type.Function{params: [], return: %Type{name: :atom}},
target: %Type.Function{params: [], return: %Type{name: :pos_integer}}}} Link to this section Summary
Functions
applies types to a function definition.
Link to this section Types
Specs
return() ::
{:ok, Type.t()}
| {:maybe, Type.t(), [Type.Message.t()]}
| {:error, Type.Message.t()}
Specs
t() :: %Type.Function{
params: [Type.t()] | :any | pos_integer(),
return: Type.t()
}
Link to this section Functions
Specs
applies types to a function definition.
Raises with Type.FunctionError if one of the following is true:
- an :any function is attempted to be applied
- a
top-arityfunction is attempted to be applied - a non-function (or union of functions) is attempted to be applied
Returns
{:ok, return_type}when the function call is successful.{:maybe, return_type, [messages]}when one or more of the parameters is overspecified.{:error, message}when any of the parameters is disjoint
Examples:
iex> import Type, only: :macros
iex> func = function ((pos_integer() -> float()))
iex> Type.Function.apply_types(func, [pos_integer()])
{:ok, %Type{name: :float}}
iex> Type.Function.apply_types(func, [non_neg_integer()])
{:maybe, %Type{name: :float}, [
%Type.Message{
type: %Type.Union{of: [%Type{name: :pos_integer}, 0]},
target: %Type{name: :pos_integer},
meta: [message: "non_neg_integer() is overbroad for argument 1 (pos_integer()) of function (pos_integer() -> float())"]
}]}
iex> Type.Function.apply_types(func, [float()])
{:error,
%Type.Message{
type: %Type{name: :float},
target: %Type{name: :pos_integer},
meta: [message: "float() is disjoint to argument 1 (pos_integer()) of function (pos_integer() -> float())"]
}}
iex> var_func = function((i -> i when i: integer()))
iex> Type.Function.apply_types(var_func, [1..10])
{:ok, 1..10}