The members of a union that are a list, a range, a context or a function less some others (D-152).
A kind that has no set of values for its parts cannot be cut as a number can. The lists that are not lists of numbers are not
a list of anything: list<Any | Null> less list<number | Null>. So the union has a member for it, {:except, base, negations}:
the values of base (a list, a range, a context or a function type) that are in none of the negations (types of the same kind).
It is the symbolic form of base ∧ ¬n1 ∧ ¬n2 ..., as a negative literal is in a decision diagram of types, and it is what makes the
four kinds closed under difference: Feel.Type.Union.difference/2 does not have to keep a kind whole when it is partly taken out.
A member is in normal form (build/2) when
baseis a plain type of the kind (never an:except) and the negations are plain types of the same kind;- each negation shares values with
base(one that does not takes nothing out) and none is within another (the smaller takes nothing the larger does not); - it has values:
empty?/2has not shown that the negations coverbase; and it has at least one negation, since without one it isbase.
The kind is the family of the member: :list, :range, :context (the open and the closed) or :function.
Deciding whether it has values
empty?/2 is the one question that the kinds differ in, and it is answered soundly: true only when the negations are
shown to cover the base. A member that is empty and not shown to be is kept, which is a type that holds more than it has to.
A list of
Pless lists ofN1,N2, ...: a list has as many elements as it likes, so it can avoid eachNiwith an element of its own. It has values unless oneNiholds all ofP(and a list that holds one is a list of that kind, however long): the lists are empty only ifPis within someNi.A range of
Pless ranges ofN1, ...: a range has two ends, and each end can avoid some of theNi. It has values if theNican be shared between the two ends so that each end has a value ofPthat none of its share holds.A context of
baseless contextsN1,N2, ...: a negation lists some entries with a type for each, so it is a box, the product of those types, and the contexts it holds are the points in it. The question is whether the box of the base is covered by the boxes of the negations, which is a search that splits the types of one entry at a time along the types that the negations list for it, and goes on to the next entry in each part (context_plans/3). An entry that the base does not list is there or not if the base is open (:absentis one of the parts) and is not there if it is closed. A negation that is closed is left by a context with an entry it does not list, which an open base may have and a closed base has if it lists one the negation does not; a negation that lists an entry a closed base cannot have holds nothing; both are dropped. The order of the entries is not a part of a type, so it plays no part. At most 4,096 parts are visited, and 256 for one entry; past that the context is taken to have values.A function has no procedure: its member is kept unless one negation holds the base (an arrow is within another when its parameters are wider and its result narrower, and a function that avoids several arrows can be made of a case for each, so the single test is the whole of it for plain arrows).
Summary
Types
A type of one family: a plain one, or an :except.
The kinds that have members of this form.
A way to be a context of the base that is in none of the negations: for each entry that matters, the part of its type that the
context has a value in, or :absent if it has none; and :extra => true if it also has an entry that no negation lists (what
leaves a closed negation when the base is open). The other entries of the base can have any value.
Functions
The plain type under a member: the member itself if it is plain.
The member that is the values of base less those of the negations, as a list of the one member, or none if it has no value
([]), or base alone if nothing is taken out.
The contexts of base that are in none of the negations, as at most limit plans, or :covered if there are none, or :bound
if the search was cut before it knew (:covered is a proof, and so is each plan).
Whether the values of base less those of the negations are shown to be none (see the module).
The member less some more plain types of its kind: build/2 of the negations it has and the new ones. The negations of a
member are in normal form already, none within another, so only the new ones are compared with them and with each other,
which for one more is as many looks as there are negations, and not as many as there are pairs of them: a dispatch on a name
takes one more type out at each test, and there are as many tests as a source has room for.
Whether a member is a type with parts of a family that :except is for.
The values in both of two members of one family (an intersection of two types is one type): the bases meet, and the negations of both are taken out.
The values of the first that are not in the second: at most one member for the base of the second taken out, and one for each of its negations (a value that is in the second's base and in a negation of it is not in the second).
The types taken out of a member.
The ways for a range of base to be in none of the negations, as at most limit pairs of types, one for each end: the values
that the start can have and the values that the end can have. :covered if there are none, :bound if the negations are
more than the search shares between two ends.
Whether every value of the first is in the second, as far as it is shown (minus/2 has no member left).
The type of every value of the family.
Types
@type atom_type() :: Feel.Type.Union.member()
A type of one family: a plain one, or an :except.
@type family() :: :list | :range | :context | :function
The kinds that have members of this form.
@type plan() :: %{ optional(String.t() | :extra) => Feel.Type.Union.t() | :absent | true }
A way to be a context of the base that is in none of the negations: for each entry that matters, the part of its type that the
context has a value in, or :absent if it has none; and :extra => true if it also has an entry that no negation lists (what
leaves a closed negation when the base is open). The other entries of the base can have any value.
Functions
The plain type under a member: the member itself if it is plain.
The member that is the values of base less those of the negations, as a list of the one member, or none if it has no value
([]), or base alone if nothing is taken out.
iex> list = {:list, [:any, :null]}
iex> Feel.Type.Except.build(list, [{:list, [:number, :null]}])
[{:except, {:list, [:any, :null]}, [{:list, [:number, :null]}]}]
iex> Feel.Type.Except.build({:list, [:number]}, [{:list, [:number, :null]}])
[]
iex> Feel.Type.Except.build({:closed_context, %{"a" => [:number]}}, [{:closed_context, %{"b" => [:string]}}])
[{:closed_context, %{"a" => [:number]}}]
@spec context_plans(atom_type(), [atom_type()], pos_integer()) :: {:found, [plan()]} | :covered | :bound
The contexts of base that are in none of the negations, as at most limit plans, or :covered if there are none, or :bound
if the search was cut before it knew (:covered is a proof, and so is each plan).
iex> base = {:closed_context, %{"a" => [:number], "b" => [:string]}}
iex> half = {:closed_context, %{"a" => [{:number, Feel.Type.NumberSet.interval(Decimal.new(0), Decimal.new(5))}], "b" => [:string]}}
iex> {:found, [plan]} = Feel.Type.Except.context_plans(base, [half], 1)
iex> Map.keys(plan)
["a"]
iex> Feel.Type.Except.context_plans(base, [base], 1)
:covered
Whether the values of base less those of the negations are shown to be none (see the module).
iex> list = {:list, [:number, :null]}
iex> Feel.Type.Except.empty?(list, [{:list, [:number, :null, :string]}])
true
iex> Feel.Type.Except.empty?({:list, [:number, :string]}, [{:list, [:number]}, {:list, [:string]}])
false
The member less some more plain types of its kind: build/2 of the negations it has and the new ones. The negations of a
member are in normal form already, none within another, so only the new ones are compared with them and with each other,
which for one more is as many looks as there are negations, and not as many as there are pairs of them: a dispatch on a name
takes one more type out at each test, and there are as many tests as a source has room for.
iex> list = {:list, [:any, :null]}
iex> [member] = Feel.Type.Except.extend(list, [{:list, [:number, :null]}])
iex> Feel.Type.Except.extend(member, [{:list, [:string, :null]}]) == Feel.Type.Except.build(list, [{:list, [:number, :null]}, {:list, [:string, :null]}])
true
@spec family(Feel.Type.Union.member()) :: family() | nil
Whether a member is a type with parts of a family that :except is for.
The values in both of two members of one family (an intersection of two types is one type): the bases meet, and the negations of both are taken out.
iex> a = {:list, [:number, :string, :null]}
iex> b = {:list, [:number, :null]}
iex> Feel.Type.Except.meet(a, {:except, {:list, [:any, :null]}, [b]})
[{:except, {:list, [:number, :string, :null]}, [{:list, [:number, :null]}]}]
The values of the first that are not in the second: at most one member for the base of the second taken out, and one for each of its negations (a value that is in the second's base and in a negation of it is not in the second).
iex> list = {:list, [:any, :null]}
iex> number = {:list, [:number, :null]}
iex> Feel.Type.Except.minus(list, number)
[{:except, {:list, [:any, :null]}, [{:list, [:number, :null]}]}]
iex> Feel.Type.Except.minus(number, list)
[]
The types taken out of a member.
@spec range_plans(atom_type(), [atom_type()], pos_integer()) :: {:found, [{Feel.Type.Union.t(), Feel.Type.Union.t()}]} | :covered | :bound
The ways for a range of base to be in none of the negations, as at most limit pairs of types, one for each end: the values
that the start can have and the values that the end can have. :covered if there are none, :bound if the negations are
more than the search shares between two ends.
iex> base = {:range, [:number, :string]}
iex> Feel.Type.Except.range_plans(base, [{:range, [:number]}, {:range, [:string]}], 2)
{:found, [{[:number], [:string]}, {[:string], [:number]}]}
iex> Feel.Type.Except.range_plans(base, [{:range, [:number, :string]}], 1)
:covered
Whether every value of the first is in the second, as far as it is shown (minus/2 has no member left).
@spec top(family()) :: Feel.Type.Union.member()
The type of every value of the family.