# `Feel.Type.Union`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/type/union.ex#L1)

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/1` raises for no member. The union of no value is `never/0`, `[]`, and it is not built but
    given, as the type of code that cannot run (D-173) and as what `intersect/2` and `difference/2` leave when no
    value is left. An expression always has a value (a failure is `null`), so the least type of one that can run
    is `[:null]`;
  * has no member twice, and the members are in a fixed order with `:null` last, 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.

# `member`

```elixir
@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()) =&gt; t()}}
  | {:closed_context, %{optional(String.t()) =&gt; t()}}
  | {:function, [t()], t()}
  | {:overload, [member(), ...], [member()]}
  | {:named, String.t()}
  | {:except, member(), [member(), ...]}
```

One alternative of a union.

# `never`

```elixir
@type never() :: []
```

The union of no value: the type of code that cannot run (`never/0`, D-173).

# `t`

```elixir
@type t() :: [member(), ...]
```

A non-empty list of members in normal form.

# `contains?`

```elixir
@spec contains?(t(), term()) :: boolean()
```

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

# `difference`

```elixir
@spec difference(t(), [member()]) :: [member()]
```

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]

# `empty_list`

```elixir
@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 `null`s (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

# `erase_sets`

```elixir
@spec erase_sets(t()) :: t()
```

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]

# `erase_top_sets`

```elixir
@spec erase_top_sets(t()) :: t()
```

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

# `format`

```elixir
@spec format(t() | never(), [{:values, boolean()}]) :: String.t()
```

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"

# `from_spec`

```elixir
@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.

# `intersect`

```elixir
@spec intersect(t(), t()) :: [member()]
```

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>"

# `kind`

```elixir
@spec kind(member()) :: member()
```

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]}

# `member_within?`

```elixir
@spec member_within?(member(), member()) :: boolean()
```

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`.

# `never`

```elixir
@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]

# `new`

```elixir
@spec new([member() | t()]) :: t()
```

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]

# `non_empty`

```elixir
@spec non_empty(member()) :: [member()]
```

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, []})
    []

# `non_empty?`

```elixir
@spec non_empty?(t()) :: boolean()
```

Whether every list of the union has an element: a list type less the empty list, or a value that is not a list.

# `nullable?`

```elixir
@spec nullable?(t()) :: boolean()
```

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

# `of_value`

```elixir
@spec of_value(term()) :: t()
```

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]

# `plain?`

```elixir
@spec plain?(t()) :: boolean()
```

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

# `positive`

```elixir
@spec positive(t() | never()) :: t() | never()
```

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]

# `positive_keeping_overloads`

```elixir
@spec positive_keeping_overloads(t() | never()) :: t() | never()
```

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

# `restrict`

```elixir
@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]

# `subtype?`

```elixir
@spec subtype?(t(), t()) :: boolean()
```

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

# `to_spec`

```elixir
@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}

# `tops`

```elixir
@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]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
