Type.Function (mavis v0.0.3) View Source

Represents a function type.

There are two fields for the struct defined by this module.

  • params a 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 :any which corresponds to "a function of any arity".
  • return the 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}}

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> Type.compare(%Type.Function{params: [], return: %Type{name: :atom}},
...>              %Type.Function{params: [], return: %Type{name: :integer}})
:gt
iex> Type.compare(%Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}},
...>              %Type.Function{params: [%Type{name: :atom}], return: %Type{name: :integer}})
:lt

intersection

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> Type.intersection(%Type.Function{params: [], return: 1..10},
...>                   %Type.Function{params: [], return: %Type{name: :integer}})
%Type.Function{params: [], return: 1..10}
iex> Type.intersection(%Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}},
...>                   %Type.Function{params: [1..10], return: %Type{name: :integer}})
%Type{name: :none}

functions with :any parameters intersected with a function with specified parameters will adopt the parameters of the intersected function.

iex> Type.intersection(%Type.Function{params: :any, return: %Type{name: :integer}},
...>                   %Type.Function{params: [1..10], return: %Type{name: :integer}})
%Type.Function{params: [1..10], return: %Type{name: :integer}}

union

Functions are generally not merged in union operations, but if their parameters are identical then their return types will be merged.

iex> Type.union(%Type.Function{params: [], return: 1..10},
...>            %Type.Function{params: [], return: 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> Type.subtype?(%Type.Function{params: [%Type{name: :integer}], return: 1..10},
...>               %Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}})
true

usable_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> Type.usable_as(%Type.Function{params: [%Type{name: :integer}], return: 1..10},
...>                %Type.Function{params: [1..10], return: %Type{name: :integer}})
:ok
iex> Type.usable_as(%Type.Function{params: [1..10], return: 1..10},
...>                %Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}})
{:maybe, [%Type.Message{type: %Type.Function{params: [1..10], return: 1..10},
                        target: %Type.Function{params: [%Type{name: :integer}], return: %Type{name: :integer}}}]}
iex> Type.usable_as(%Type.Function{params: [], return: %Type{name: :atom}},
...>                %Type.Function{params: [], return: %Type{name: :integer}})
{:error, %Type.Message{type: %Type.Function{params: [], return: %Type{name: :atom}},
                       target: %Type.Function{params: [], return: %Type{name: :integer}}}}

Link to this section Summary

Link to this section Types

Specs

t() :: %Type.Function{
  inferred: boolean(),
  params: [Type.t()] | :any,
  return: Type.t()
}