How types relate to each other (DMN 1.6, sections 10.3.2.9.1 and 10.3.2.9.2).
Two relations are defined. Equivalence means two types can be used
interchangeably. Conformance, written T <: S, means an instance of T can be
used wherever an instance of S is expected. The rules for acyclic model types are structural:
names are resolved to shapes. Guarded recursive descriptors accept identical reachable declaration
graphs up to consistent renaming (Feel.Type.Recursive.equivalent?/2) and the applicable ordinary
lattice rules, without general recursive subtyping or equivalence.
A negative answer can therefore mean that a recursive relation was not proved. Structural
analysis projections must not be substituted for exact recursive function result declarations.
Null <: T <: Anyfor every typeT;Anyconforms only toAny.- A primitive type conforms only to itself. In particular
datedoes not conform todate and time, and the two durations are unrelated. list<T> <: list<S>andrange<T> <: range<S>whenT <: S.context<M> <: context<N>when every member ofNis inMwith a conforming type. Extra members inMare allowed, so the context{id, name, age}conforms to{id, name}but not the other way round.function<P> -> R <: function<Q> -> Swhen the arities are equal, each parameter ofQconforms to the parameter ofP(contravariant), andR <: S(covariant). Parameter names are not part of the type.
A type marked {:non_null, T} (D-48, number!) is T without Null, which is under every
other type:
T! <: T, andT! <: S!andT! <: SwhenT <: S.Nullis not under a marked type, and neither is a type that can beNull:numberis not undernumber!.- The join of two marked types is marked; the join of a marked type and another is the join of the types without the mark.
A type that is {:not, T} or {:union, [T, ...]} (D-178, Feel.Type.Composite) is not in the lattice, which has no union and no
negation. A relation that has one is asked of the unions the two types make (Feel.Type.Union.subtype?/2, sound, and the same
answer as the rules above for the types the rules can answer).
All of these work on resolved types: a {:named, name} type is related only to
itself, to Any, and (as Null) from Null. Resolve model-defined types first
with Feel.Type.resolve/2.
Summary
Functions
Returns true when the first type conforms to the second.
Returns true when two types are equivalent.
Returns a conservative common supertype: the type of a list with elements of these two types. This need not be the least expressible supertype for functions or composite declarations.
Functions
@spec conforms?(Feel.Type.t(), Feel.Type.t()) :: boolean()
Returns true when the first type conforms to the second.
iex> Feel.Type.Conformance.conforms?(:null, {:list, :number})
true
iex> Feel.Type.Conformance.conforms?(:null, {:non_null, :number})
false
iex> Feel.Type.Conformance.conforms?({:non_null, :number}, :number)
true
iex> Feel.Type.Conformance.conforms?(:number, {:non_null, :number})
false
iex> Feel.Type.Conformance.conforms?(:date, :date_and_time)
false
iex> Feel.Type.Conformance.conforms?({:context, %{"id" => :number, "age" => :number}}, {:context, %{"id" => :number}})
true
@spec equivalent?(Feel.Type.t(), Feel.Type.t()) :: boolean()
Returns true when two types are equivalent.
Equal terms are equivalent. Composite declarations can have different terms for the same type (for example, a union's alternatives in another order), so otherwise both directions of conformance must hold.
iex> Feel.Type.Conformance.equivalent?(
...> {:context, %{"a" => :number, "b" => :string}},
...> {:context, %{"b" => :string, "a" => :number}}
...> )
true
iex> Feel.Type.Conformance.equivalent?({:list, :number}, {:list, :any})
false
@spec join(Feel.Type.t(), Feel.Type.t()) :: Feel.Type.t()
Returns a conservative common supertype: the type of a list with elements of these two types. This need not be the least expressible supertype for functions or composite declarations.
Nullis absorbed: the join ofNullandTisT.- If one type conforms to the other, the join is the upper one.
- Lists and ranges join their element types, and two contexts join to the members they share, each with the join of its types.
- Anything else, such as
numberandstring, joins toAny.
Examples
iex> Feel.Type.Conformance.join(:number, :null)
:number
iex> Feel.Type.Conformance.join({:list, :number}, {:list, :string})
{:list, :any}
iex> Feel.Type.Conformance.join(:number, :string)
:any