Feel.Type.Except (feel v0.1.0)

Copy Markdown View Source

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

  • base is 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?/2 has not shown that the negations cover base; and it has at least one negation, since without one it is base.

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 P less lists of N1, N2, ...: a list has as many elements as it likes, so it can avoid each Ni with an element of its own. It has values unless one Ni holds all of P (and a list that holds one is a list of that kind, however long): the lists are empty only if P is within some Ni.

  • A range of P less ranges of N1, ...: a range has two ends, and each end can avoid some of the Ni. It has values if the Ni can be shared between the two ends so that each end has a value of P that none of its share holds.

  • A context of base less contexts N1, 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 (:absent is 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

atom_type()

@type atom_type() :: Feel.Type.Union.member()

A type of one family: a plain one, or an :except.

family()

@type family() :: :list | :range | :context | :function

The kinds that have members of this form.

plan()

@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

base(member)

@spec base(atom_type()) :: atom_type()

The plain type under a member: the member itself if it is plain.

build(base, negations)

@spec build(atom_type(), [atom_type()]) :: [atom_type()]

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

context_plans(base, negations, limit)

@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

empty?(base, negations)

@spec empty?(atom_type(), [atom_type()]) :: boolean()

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

extend(member, more)

@spec extend(atom_type(), [atom_type()]) :: [atom_type()]

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

family(arg1)

@spec family(Feel.Type.Union.member()) :: family() | nil

Whether a member is a type with parts of a family that :except is for.

meet(a, b)

@spec meet(atom_type(), atom_type()) :: [atom_type()]

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

minus(a, b)

@spec minus(atom_type(), atom_type()) :: [atom_type()]

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

negations(arg1)

@spec negations(atom_type()) :: [atom_type()]

The types taken out of a member.

range_plans(arg1, negations, limit)

@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

subset?(a, b)

@spec subset?(atom_type(), atom_type()) :: boolean()

Whether every value of the first is in the second, as far as it is shown (minus/2 has no member left).

top(atom)

@spec top(family()) :: Feel.Type.Union.member()

The type of every value of the family.