Feel.Type.Composite (feel v0.1.0)

Copy Markdown View Source

Declared types that go past the specification's lattice: {:not, type} and {:union, [type, ...]} (D-178).

The lattice has no union and no negation, and the internal type (Feel.Type.Union) has both: Any except number is what is left of a value of v instance of number in the else branch, number | string is a join the analysis makes at every if. A host that knows more of a name than the lattice can say, that it is anything but a number or that it is a number or a string, says it with these two, as it says "never null" with {:non_null, type} (D-48):

Feel.Type.t/0WrittenThe values
{:not, :number}Any except numberevery value that is not a number
{:union, [:number, :string]}number | stringa number or a string
{:not, {:union, [:number, {:list, :any}]}}Any except number | list<Any>neither a number nor a list

What they mean

The union translation (to_union/1) supplies the analysis representation. Runtime membership evaluates the union or negation directly with the original component declarations. Recursion may occur in a value's retained function signature even when the target declaration has none, so an analysis projection cannot replace the runtime check. In particular, negating a recursive declaration projects to Any | Null without making runtime negation accept every value.

  • null is in the type as it is in every other type of a declaration: the run exempts null from a declared type (D-138), so an analysis that left it out would be wrong. Only a mark on the whole type, {:non_null, type} or (Any except number)!, takes it out. A mark on a member of a union, or on the type after except, is ignored by to_union/1 and refused by Feel.Type.parse/2: Any except number! would read as "not a number, and not null", and it is not.
  • {:not, type} is Any less the values of type that are not null. Null is not a value that can be taken out (null is under every type), so {:not, :null} is Any, and {:not, :any} is Null.
  • {:union, types} is the join of the types, and has at least one. A member that is a composite type is flattened by the union's normal form, so number | (string | boolean) is number | string | boolean.
  • Conversion is the one of the specification, asked of the union: a singleton list is unwrapped when its element is in the type and the list is not (Feel.Type.Conversion). A value is not wrapped in a list, and a date is not made a date and time, to reach a member of a union (3 is not list<number> | string, and is not made [3]), since the specification's conversions are for one type that is expected, and a union is not one. A list whose element type is a composite type is wrapped as any list is.
  • A function type with a composite type in its parameters or its result is compared by the algebra of arrows (Feel.Type.Arrows), not by the lattice. Composite conformance involving recursive descriptors conservatively accepts identical declarations; it does not prove recursive relations from projections.

Feel.type/1 is a type of the specification, so for a name declared with one it is wider than the declaration: Any for Any except number and for number | string, list<Any> for list<number> | list<string>. Feel.IR.type/1 retains the internal representation, including conservative upper approximations where required.

Summary

Functions

Whether a type is one of the two, at its outermost level. A composite type inside a list, a context or a function type is reached by the code that walks the type.

Whether a type is proved within another when either is composite. Non-recursive declarations compare their analysis unions. If either contains guarded recursive descriptors, only identical declarations are accepted here; a negative answer need not establish semantic non-inclusion.

The analysis union of a composite declaration, null included. Recursive declarations may give an upper approximation rather than exact runtime membership. Raises ArgumentError for a union of no type.

Whether a value is in a composite type. null is exempt; each component is checked directly against the original declaration, including retained function signatures.

Functions

composite?(arg1)

@spec composite?(Feel.Type.t()) :: boolean()

Whether a type is one of the two, at its outermost level. A composite type inside a list, a context or a function type is reached by the code that walks the type.

iex> Feel.Type.Composite.composite?({:not, :number})
true
iex> Feel.Type.Composite.composite?({:list, {:not, :number}})
false

conforms?(type, target)

@spec conforms?(Feel.Type.t(), Feel.Type.t()) :: boolean()

Whether a type is proved within another when either is composite. Non-recursive declarations compare their analysis unions. If either contains guarded recursive descriptors, only identical declarations are accepted here; a negative answer need not establish semantic non-inclusion.

iex> Feel.Type.Composite.conforms?(:number, {:union, [:number, :string]})
true
iex> Feel.Type.Composite.conforms?({:not, :number}, :any)
true
iex> Feel.Type.Composite.conforms?({:union, [:number, :string]}, :number)
false

to_union(arg)

@spec to_union(Feel.Type.t()) :: Feel.Type.Union.t()

The analysis union of a composite declaration, null included. Recursive declarations may give an upper approximation rather than exact runtime membership. Raises ArgumentError for a union of no type.

iex> Feel.Type.Composite.to_union({:not, :number}) |> Feel.Type.Union.format()
"Any except number | Null"
iex> Feel.Type.Composite.to_union({:union, [:number, :string]}) |> Feel.Type.Union.format()
"number | string | Null"
iex> Feel.Type.Composite.to_union({:not, {:union, [:number, :string]}}) |> Feel.Type.Union.format()
"Any except number, string | Null"
iex> Feel.Type.Composite.to_union({:not, {:list, :number}}) |> Feel.Type.Union.format()
"Any except list<number | Null> | Null"
iex> Feel.Type.Composite.to_union({:union, []})
** (ArgumentError) a union type needs a member

value_conforms?(value, arg2)

@spec value_conforms?(Feel.Value.t(), Feel.Type.t()) :: boolean()

Whether a value is in a composite type. null is exempt; each component is checked directly against the original declaration, including retained function signatures.

iex> Feel.Type.Composite.value_conforms?(Decimal.new(1), {:not, :number})
false
iex> Feel.Type.Composite.value_conforms?("a", {:not, :number})
true
iex> Feel.Type.Composite.value_conforms?(nil, {:not, :number})
true