Types with the null made explicit, for inference (D-39).
Feel.Type is the type system of the specification, where Null conforms to every
type: number means "a number or null". That is right for conformance and for
instance of, but it cannot say that a result is never null, as 1 < 2
is, or that it is null, as 1 < null is. A union can. It is a list of the
alternatives a value may have, and :null is one of them when the value may be
null:
| Union | The values |
|---|---|
[:number] | a number, never null |
[:number, :null] | a number or null (the spec's number) |
[:null] | null only |
[:boolean, :string] | a boolean or a string, never null |
[:any, :null] | any value (the spec's Any) |
The members are the primitive types of Feel.Type, :null, and these, whose
parts are unions in turn: {:list, union}, {:range, union},
{:context, %{"key" => union}}, {:closed_context, %{"key" => union}},
{:function, [union], union}, and {:named, "Name"}. :any means any value that is not null.
Any, taken apart (D-39)
:any is the union of the kinds of value, and a union can say which of them are left. The kinds are the tops
(tops/0): the bare :boolean, :number, :string, :date, :time, :date_and_time and the two durations,
the list, the range and the context of anything ({:list, [:any, :null]}, {:range, [:any, :null]},
{:context, %{}}), :function, the function of any parameters, and :foreign, a term that is not a value of
FEEL (Feel.Runtime.Kind.of/1 is :unknown for it: the host binds what it likes, and v instance of Any is
true for it). A union that has all of them is :any, and difference/2 takes some out: Any less
number is the other tops, which is written Any except number. :function and :foreign are not
written by anyone; they are what is left of an Any that something was taken from.
A kind less some of it (D-152)
A list, a range, a context and a function have no set of values to cut, so what is left of Any when some of one is
taken out is a member of its own: {:except, base, negations} (Feel.Type.Except), the values of base that are in none of
the negations. difference/2 makes them, intersect/2, subtype?/2 and contains?/2 know them, and format/2 writes them
(Any except list<number | Null>). Normalization preserves nested exclusions. A reader can ask
for a positive approximation at the level it inspects (positive/1, Feel.IR.plain_type/1);
this does not rewrite parts under negation or widen function parameters.
An overload (D-171)
A built-in function with several signatures is all of its function types, and its type is {:overload, arrows, refinements}
(Feel.Type.Arrows): the arrows are what subtype?/2, difference/2 and contains?/2 see (a function type is within an
overload if it is within each arrow, and an overload within a function type if one of its arrows is), and a refinement is what a
call does for the arguments in its parameters, which types a call and nothing else. Function literals also use
their observable runtime signature bound as an arrow and their body's more precise result as a call refinement.
positive/1 makes a top-level overload its arrows as alternatives, and
positive_keeping_overloads/1 keeps it for the readers that compare and call functions. A union has at most four of them.
Open and closed contexts (D-65)
A context type of the specification is open: context<a: number> is the type of every context that
has an entry a that is a number, and may have others (Feel.Type.Conformance). That is what
{:context, members} is, and it is what a declared type is. A value has a closed type, though, the entries it
has and no others, and so has a context literal: {:closed_context, members}. A closed context is a
subtype of the open one with the same entries, so everything that holds for an open type holds for it, and
it knows more: that an entry it does not list is not there (Feel.Analysis.Paths). to_spec/1 writes both as the
type of the specification, and format/2 writes both as context<...>; from_spec/1 never makes a closed one.
Value sets
A number, a string, or a boolean alternative can say which values it has (D-39):
{:number, set} with a Feel.Type.NumberSet, {:string, strings} with up to 64 strings, or a Feel.Type.StringSet where there are intervals of strings, or strings left out of them (D-39), and
{:boolean, true} or {:boolean, false}. The bare :number is every number, so it is the
alternative that holds any {:number, set}. [{:number, [0..150]}, :null] is a number from 0
to 150, or null; 1 has the type [{:number, [1..1]}], and 1 < 2 has [{:boolean, true}].
to_spec/1 leaves the sets out, so a type of the specification does not see them.
A date and the two kinds of duration have sets too (D-109): {:date, set},
{:years_and_months_duration, set} and {:days_and_time_duration, set}, each a
Feel.Type.NumberSet of the numbers that stand for the values, their image (Feel.Type.Image):
the days of a date, the months or the seconds of a duration. @"PT2H" has the type
[{:days_and_time_duration, [7200..7200]}], written days and time duration = PT2H. The bare
kind is every value of the kind, as :number is. So have a time and a date and time (D-109): {:time, set}
and {:date_and_time, set}, of the second a value falls in on one of two lines, for the values with no zone and
for those with one, since the order of two depends on their zones (Feel.Type.Moment).
Normal form
Build a union with new/1, and do not match on one outside this module. A union
- is not empty:
new/1raises for no member. The union of no value isnever/0,[], and it is not built but given, as the type of code that cannot run (D-173) and as whatintersect/2anddifference/2leave when no value is left. An expression always has a value (a failure isnull), so the least type of one that can run is[:null]; - has no member twice, and the members are in a fixed order with
:nulllast, so two unions of the same alternatives are equal; - is just
:any(and:null, if it is there) when it has:any, which contains every other member; - has one alternative for the numbers, one for the strings, one for the booleans, one for the
dates, and one for each kind of duration, at most: the sets of several are joined, a bare
:number(:date, ...) holds the rest, a set of every value of the kind is the bare one, and an alternative with no value in it is dropped.
Members with parts are not merged beyond that: list<number> and list<number | null> stay
two members, though the second contains the first.
Two type languages
from_spec/1 reads a type of the specification into a union by adding :null at
every level. to_spec/1 goes back: it drops :null and joins what is left with
Feel.Type.join/2, so [:number, :string] is :any. The second loses information,
and the first adds a null that may not be there, so a union is for inference and
the type of the specification is for everything else, Feel.type/1 included.
Function parameter projection preserves outer non-null marks and retains an arrow only when
the projected domain is within the original domain; otherwise the whole function becomes Any.
of_value/1 describes a value using the representation's scalar and structural bounds. Recursive function signatures use upper
observable arrows and keep structural call bounds as refinements. contains?/2 asks whether a
value is one of the alternatives in this analysis representation.
Summary
Functions
Returns true when the value is one of the alternatives of the union.
The values that are in the first union and not in the second: a union that holds every value that is, and as few others as it can say (D-39).
The type of the empty list alone, []: a list with no element that it could have. Every list<T> holds it, and
list<Null> holds it and the lists of nulls (Table 43), which is what a reader that does not ask for the exact type sees.
The union with every set of values left out, at every level: each alternative that is a set (number in [1..5], "a", true, a date in a range) is its bare kind, which holds every value of the kind. null, the
shape of a list, a context and a range, and the names of types are kept.
The union with the sets left out of its own alternatives, and not of the types inside them: list<number in [1..3]> stays as
it is. It is erase_sets/1 for a union whose parts have no sets already, which is the case of a type that is made from the
types of the nodes below it in the core tier (Feel.Analysis.Refinement), and costs the size of the union and not the size of
the type, so that a list nested to a thousand levels is not walked at each level.
Writes a union for a message: the members joined with |.
Reads a type of the specification, where every type includes null.
The values that two unions have in common: a union that holds every value that is in both, and as few others as it can say (D-194).
The kind of an alternative: the primitive type that a set of values is of, so :number for
{:number, set}, and the alternative itself for every other.
Whether one member is within another by itself: the structural test of subtype?/2 for two members, without looking at
the other members of a union. For Feel.Type.Except.
Returns the union of no value, []: the type of code that cannot run (D-173), such as a branch of an if that its
condition or the facts of the conditions around it rule out.
Builds a union from members and unions, in normal form.
The lists of a list type that have an element: the type less the empty list (Feel.Type.Except).
Whether every list of the union has an element: a list type less the empty list, or a value that is not a list.
Returns true when the union has null among its alternatives.
Returns the internal type of a value, retaining literal sets where the representation supports them.
Whether no member of the union is a kind less some of it or the empty list (D-152): the union as a reader that looks into a type sees it.
The union with the kinds that are less some of them (Feel.Type.Except) as the whole kind: every value that the union holds,
and in a form that does not look into what is taken out. It is what a reader of a type that does not ask for the exact one
reads (Feel.IR.plain_type/1).
The union as positive/1 has it, with the overloads kept (D-171): what a reader that compares and applies function types reads.
A kind less some of it is the kind, and an overload is still all of its arrows, since a function that is passed on or called is
the one that the overload is the type of.
Narrows the numbers, the strings, the booleans, the dates or the durations of a union to a set.
Returns true when every alternative of the first union is covered by one of the
second.
Writes a union as a type of the specification: null is dropped, because every type
includes it, and what is left is joined with Feel.Type.join/2.
The kinds of value that :any is the union of, each as the member that holds all of its values (D-39).
Types
@type member() :: :any | :null | :boolean | :number | :string | :date | :time | :date_and_time | :years_and_months_duration | :days_and_time_duration | :function | :foreign | {:number, Feel.Type.NumberSet.t()} | {:date, Feel.Type.NumberSet.t()} | {:time, Feel.Type.NumberSet.t()} | {:date_and_time, Feel.Type.NumberSet.t()} | {:years_and_months_duration, Feel.Type.NumberSet.t()} | {:days_and_time_duration, Feel.Type.NumberSet.t()} | {:string, [String.t(), ...] | Feel.Type.StringSet.t()} | {:boolean, boolean()} | {:list, t()} | {:range, t()} | {:context, %{optional(String.t()) => t()}} | {:closed_context, %{optional(String.t()) => t()}} | {:function, [t()], t()} | {:overload, [member(), ...], [member()]} | {:named, String.t()} | {:except, member(), [member(), ...]}
One alternative of a union.
@type never() :: []
The union of no value: the type of code that cannot run (never/0, D-173).
@type t() :: [member(), ...]
A non-empty list of members in normal form.
Functions
Returns true when the value is one of the alternatives of the union.
Lists, ranges and contexts use their component unions, including their explicit null alternatives.
Functions use observable projected arrows and ignore call refinements for membership. This asks
about the analysis representation; use Feel.Type.value_conforms?/2 with the original declaration
for complete guarded recursive data membership and retained runtime signature checks.
Any term may be asked about, since a binding is the host's: one that is not a FEEL value is
in the union only through Any.
iex> Feel.Type.Union.contains?([:number, :null], nil)
true
iex> Feel.Type.Union.contains?([:number], nil)
false
iex> Feel.Type.Union.contains?([:number, :string], "a")
true
iex> Feel.Type.Union.contains?([:any], %{"a" => 1})
true
The values that are in the first union and not in the second: a union that holds every value that is, and as few others as it can say (D-39).
:any is taken apart into its kinds (tops/0), and each alternative loses what the second union has of its kind:
the numbers, the strings, the booleans, the dates and the durations by their sets, and a kind that has no sets
(a list, a range, a context, a function) all of it or none, by subtype?/2. A time and a date and time lose the lines
of a set (Feel.Type.Moment.complement/2, D-152): what a set of lines can say of what is left, and more where it cannot. So the result is never smaller than the difference, and is the difference where
the sets are: it is a sound answer to "what is left", which is what an else branch needs. It is empty
(not a union) when nothing is left. null is left unless the second union has it.
iex> Feel.Type.Union.difference([:any, :null], [:number]) |> Feel.Type.Union.format()
"Any except number | Null"
iex> Feel.Type.Union.difference([:number, :string], [:number])
[:string]
iex> Feel.Type.Union.difference([:number], [:number])
[]
iex> five = Feel.Type.NumberSet.point(Decimal.new(5))
iex> Feel.Type.Union.difference([:number], [{:number, five}]) |> Feel.Type.Union.format()
"number in [-max..5), (5..max]"
iex> Feel.Type.Union.difference([:boolean], [{:boolean, true}])
[{:boolean, false}]
iex> Feel.Type.Union.difference([:any, :null], [:any])
[:null]
@spec empty_list() :: {:list, []}
The type of the empty list alone, []: a list with no element that it could have. Every list<T> holds it, and
list<Null> holds it and the lists of nulls (Table 43), which is what a reader that does not ask for the exact type sees.
iex> Feel.Type.Union.empty_list()
{:list, []}
iex> Feel.Type.Union.format([Feel.Type.Union.empty_list()])
"empty list"
iex> Feel.Type.Union.contains?([Feel.Type.Union.empty_list()], [])
true
iex> Feel.Type.Union.contains?([Feel.Type.Union.empty_list()], [nil])
false
The union with every set of values left out, at every level: each alternative that is a set (number in [1..5], "a", true, a date in a range) is its bare kind, which holds every value of the kind. null, the
shape of a list, a context and a range, and the names of types are kept.
A function arrow is kept only when erasing its parameters does not broaden their domains:
broader parameters would strengthen the arrow rather than safely widen it. Otherwise the
whole arrow becomes the :function kind. Overloads fall back likewise when an observable arrow
cannot be retained safely, and discard call refinements that cannot be erased safely.
The result holds every value that the union holds, so it is a type of the same values, and a less exact one. It is
the type that an analysis without value sets gives (Feel.Analysis.Refinement).
iex> Feel.Type.Union.erase_sets([{:number, Feel.Type.NumberSet.point(Decimal.new(3))}])
[:number]
iex> Feel.Type.Union.erase_sets([{:boolean, true}, :null])
[:boolean, :null]
iex> Feel.Type.Union.erase_sets([{:string, ["a", "b"]}, {:number, Feel.Type.NumberSet.all()}])
[:number, :string]
iex> Feel.Type.Union.of_value(%{"a" => [Decimal.new(1)]}) |> Feel.Type.Union.erase_sets()
[{:closed_context, %{"a" => [{:list, [:number]}]}}]
iex> Feel.Type.Union.erase_sets([{:function, [[{:number, Feel.Type.NumberSet.point(Decimal.new(1))}]], [{:boolean, false}]}])
[:function]
iex> Feel.Type.Union.erase_sets([:any, :null])
[:any, :null]
The union with the sets left out of its own alternatives, and not of the types inside them: list<number in [1..3]> stays as
it is. It is erase_sets/1 for a union whose parts have no sets already, which is the case of a type that is made from the
types of the nodes below it in the core tier (Feel.Analysis.Refinement), and costs the size of the union and not the size of
the type, so that a list nested to a thousand levels is not walked at each level.
iex> Feel.Type.Union.erase_top_sets([{:number, Feel.Type.NumberSet.point(Decimal.new(3))}, :null])
[:number, :null]
iex> inner = [{:number, Feel.Type.NumberSet.point(Decimal.new(3))}]
iex> Feel.Type.Union.erase_top_sets([{:list, inner}]) == [{:list, inner}]
true
Writes a union for a message: the members joined with |.
The sets of values are written too (number in [0..150]), unless values: false is given,
which writes a number as number, a string as string, a boolean as boolean, and a date or a
duration as its kind. The values of a date or a duration are written as 2024-01-31 and
P1DT2H (Feel.Type.Image.format_value/2).
iex> Feel.Type.Union.format([:number, :null])
"number | Null"
iex> Feel.Type.Union.format([{:list, [:string]}])
"list<string>"
iex> union = Feel.Type.Union.of_value(Decimal.new(3))
iex> {Feel.Type.Union.format(union), Feel.Type.Union.format(union, values: false)}
{"number = 3", "number"}
iex> Feel.Type.Union.format(Feel.Type.Union.never())
"Never"
@spec from_spec(Feel.Type.t()) :: t()
Reads a type of the specification, where every type includes null.
A type marked {:non_null, type} (D-48) is the exception, for its outermost level: it has
no null. What is inside it, as the elements of a list, is read as the specification has it.
Raises ArgumentError for Null!, which has no values. A negation and a union that a host declares
({:not, type}, {:union, types}, D-178) are the union they make (Feel.Type.Composite.to_union/1).
iex> Feel.Type.Union.from_spec(:number)
[:number, :null]
iex> Feel.Type.Union.from_spec(:null)
[:null]
iex> Feel.Type.Union.from_spec({:list, :number})
[{:list, [:number, :null]}, :null]
iex> Feel.Type.Union.from_spec({:non_null, :number})
[:number]
iex> Feel.Type.Union.from_spec({:non_null, {:list, {:non_null, :number}}})
[{:list, [:number]}]Recursive declarations are projected to a finite structural upper bound, not an exact translation.
Recursive domains use a lower parameter bound in an upper arrow, and recursive negations project to top.
Recursive function results use Any | Null in observable arrows, retaining their structural upper
bound only as a call refinement. A graph projection may conservatively widen that call bound too.
A consumer proving a recursive target must retain its check; inclusion in this projection is insufficient.
The values that two unions have in common: a union that holds every value that is in both, and as few others as it can say (D-194).
It is empty, never/0, when there is no value in both: a caller that gets [] knows that the two cannot
be the same value. A value is in a union by what it is, so the
numbers 1 and 1.0 are one value, and 1 is not in string.
The alternatives are taken in pairs. The sets of a kind meet in their intersection; Null meets only
Null, and Any (every value that is not null) meets anything else as it is. The parts of a list and
of a range meet part by part, and a list of nothing in common is the list of nothing, [] (Table 43). Two
contexts meet entry by entry. An open context needs the entries it lists to be there (D-23), so it meets a
closed one only if the closed one has them all; two closed ones only if they have the same entries. A
named type is not looked into, and a function type meets another as the first.
iex> Feel.Type.Union.intersect([:number, :null], [:string, :null])
[:null]
iex> Feel.Type.Union.intersect([:number], [:string])
[]
iex> Feel.Type.Union.intersect([:any, :null], [:number]) |> Feel.Type.Union.format()
"number"
iex> a = [{:list, [:any, :null]}]
iex> b = [{:list, [:number]}]
iex> Feel.Type.Union.intersect(a, b) |> Feel.Type.Union.format()
"list<number>"
The kind of an alternative: the primitive type that a set of values is of, so :number for
{:number, set}, and the alternative itself for every other.
iex> Feel.Type.Union.kind({:number, Feel.Type.NumberSet.all()})
:number
iex> Feel.Type.Union.kind({:boolean, true})
:boolean
iex> Feel.Type.Union.kind({:days_and_time_duration, Feel.Type.NumberSet.all()})
:days_and_time_duration
iex> Feel.Type.Union.kind({:list, [:number]})
{:list, [:number]}
Whether one member is within another by itself: the structural test of subtype?/2 for two members, without looking at
the other members of a union. For Feel.Type.Except.
@spec never() :: never()
Returns the union of no value, []: the type of code that cannot run (D-173), such as a branch of an if that its
condition or the facts of the conditions around it rule out.
It is not [:null], the type of code that runs and gives null. It is the bottom of the order: it is within every
type, nothing but itself is within it, and it is the identity of a join (new/1 leaves it out of what it builds). A
value cannot be in it, so a node of this type that a run reaches is a type that was wrong (Feel.Paranoid says so).
iex> Feel.Type.Union.never()
[]
iex> Feel.Type.Union.subtype?(Feel.Type.Union.never(), [:number])
true
iex> Feel.Type.Union.new([Feel.Type.Union.never(), [:number]])
[:number]
Builds a union from members and unions, in normal form.
Raises ArgumentError when there is nothing to build it from.
iex> Feel.Type.Union.new([:null, :number, :number])
[:number, :null]
iex> Feel.Type.Union.new([[:string, :null], [:number]])
[:number, :string, :null]
iex> Feel.Type.Union.new([:number, :any])
[:any]
iex> Feel.Type.Union.new([:any, :null, :boolean])
[:any, :null]
The lists of a list type that have an element: the type less the empty list (Feel.Type.Except).
iex> Feel.Type.Union.non_empty({:list, [:number, :null]}) |> Feel.Type.Union.format()
"non-empty list<number | Null>"
iex> Feel.Type.Union.non_empty({:list, []})
[]
Whether every list of the union has an element: a list type less the empty list, or a value that is not a list.
Returns true when the union has null among its alternatives.
iex> Feel.Type.Union.nullable?([:number, :null])
true
iex> Feel.Type.Union.nullable?([:number])
false
Returns the internal type of a value, retaining literal sets where the representation supports them.
Function values describe observable signature bounds and call refinements. Recursive result signatures are approximated conservatively, so this is not an exact runtime membership predicate.
Unlike Feel.Type.of/1, which follows Table 43, the elements of a list are not
joined: ["a", true] is a list of boolean | string, not of Any, and null is
an alternative of the element type where there is one. A term that is not a FEEL
value is [:any, :null]: nothing is known about it.
iex> Feel.Type.Union.of_value(Decimal.new(1)) |> Feel.Type.Union.format()
"number = 1"
iex> Feel.Type.Union.of_value(nil)
[:null]
iex> Feel.Type.Union.of_value([true, nil]) |> Feel.Type.Union.format()
"non-empty list<boolean = true | Null>"
iex> Feel.Type.Union.of_value([]) |> Feel.Type.Union.format()
"empty list"
iex> Feel.Type.Union.of_value(~D[2024-01-31]) |> Feel.Type.Union.format()
"date = 2024-01-31"
iex> Feel.Type.Union.of_value(Feel.Value.DaysAndTimeDuration.new(93_600)) |> Feel.Type.Union.format()
"days and time duration = P1DT2H"
iex> Feel.Type.Union.of_value(1.5)
[:any, :null]
Whether no member of the union is a kind less some of it or the empty list (D-152): the union as a reader that looks into a type sees it.
iex> Feel.Type.Union.plain?([:number, :null])
true
iex> Feel.Type.Union.plain?([Feel.Type.Union.empty_list()])
false
The union with the kinds that are less some of them (Feel.Type.Except) as the whole kind: every value that the union holds,
and in a form that does not look into what is taken out. It is what a reader of a type that does not ask for the exact one
reads (Feel.IR.plain_type/1).
iex> list = {:list, [:any, :null]}
iex> union = Feel.Type.Union.new([{:except, list, [{:list, [:number, :null]}]}, :null])
iex> Feel.Type.Union.positive(union)
[{:list, [:any, :null]}, :null]
iex> Feel.Type.Union.positive([:number, :null])
[:number, :null]
The union as positive/1 has it, with the overloads kept (D-171): what a reader that compares and applies function types reads.
A kind less some of it is the kind, and an overload is still all of its arrows, since a function that is passed on or called is
the one that the overload is the type of.
iex> {:ok, abs} = Feel.Builtin.value("abs")
iex> union = Feel.Type.Union.of_value(abs)
iex> Feel.Type.Union.positive_keeping_overloads(union) == union
true
iex> length(Feel.Type.Union.positive(union))
1
@spec restrict( t(), :number | :string | :boolean | Feel.Type.Image.kind(), Feel.Type.NumberSet.t() | Feel.Type.StringSet.t() | [String.t()] | [boolean()] ) :: t()
Narrows the numbers, the strings, the booleans, the dates or the durations of a union to a set.
set is a Feel.Type.NumberSet for :number and for a kind with an image (:date and the two
durations, whose sets are those of the numbers that stand for the values), a list of strings for
:string, and a list of true and false for :boolean. The alternative that is a number (a
string, a boolean) becomes the numbers (strings, booleans) that are in both sets, and goes if none
is. A union that has no alternative left is [:null]: the only value it can have is null.
iex> set = Feel.Type.NumberSet.interval(Decimal.new(0), Decimal.new(150))
iex> Feel.Type.Union.restrict([:number, :null], :number, set) |> Feel.Type.Union.format()
"number in [0..150] | Null"
iex> Feel.Type.Union.restrict([:string, :null], :string, ["a", "b"])
[{:string, ["a", "b"]}, :null]
iex> Feel.Type.Union.restrict([:number, :null], :number, [])
[:null]
iex> Feel.Type.Union.restrict([:boolean, :null], :boolean, [true])
[{:boolean, true}, :null]
iex> Feel.Type.Union.restrict([:boolean, :null], :boolean, [true, false])
[:boolean, :null]
Returns true when every alternative of the first union is covered by one of the
second.
This is sound but not complete: a union of lists is compared member by member, and
list<number | string> is not seen to be within list<number> | list<string> (it
is not), nor is a context with a union as a member seen to be within the union of
two contexts (it is). The two type languages agree on what they share: for types of
the non-recursive specification fragment, subtype?(from_spec(a), from_spec(b)) agrees with Feel.Type.conforms?(a, b). Recursive projections are upper approximations and do not establish that equivalence.
iex> Feel.Type.Union.subtype?([:number], [:number, :null])
true
iex> Feel.Type.Union.subtype?([:number, :null], [:number])
false
iex> Feel.Type.Union.subtype?([{:list, [:number]}], [{:list, [:number, :null]}, :null])
true
@spec to_spec(t()) :: Feel.Type.t()
Writes a union as a type of the specification: null is dropped, because every type
includes it, and what is left is joined with Feel.Type.join/2.
iex> Feel.Type.Union.to_spec([:number, :null])
:number
iex> Feel.Type.Union.to_spec([:null])
:null
iex> Feel.Type.Union.to_spec([:boolean, :string])
:any
iex> Feel.Type.Union.to_spec([{:list, [:number]}])
{:list, :number}
@spec tops() :: [member(), ...]
The kinds of value that :any is the union of, each as the member that holds all of its values (D-39).
iex> Feel.Type.Union.tops() |> Enum.take(3)
[:boolean, :number, :string]
iex> Feel.Type.Union.new(Feel.Type.Union.tops() ++ [:null])
[:any, :null]