Feel.Type.Instance (feel v0.1.0)

Copy Markdown View Source

How values relate to types (DMN 1.6, sections 10.3.2.9 and 10.3.2.15, Tables 43 and 62).

of/1 gives the type of a value. value_conforms?/2 asks whether a value fits a type, element by element, and instance?/2 is the instance of operator, which differs only in how it treats null.

null

null conforms to every type, because Null <: T. That is what lets missing data flow through typed variables and parameters, and what makes a context member set to null fit its type. But null instance of number is false, and so is null instance of Any: Table 62 makes null an instance of Null and nothing else. That rule is in instance?/2 alone, so it can be changed in one place.

Missing members

A value fits a context type only if every member of the type is present in the value. A member that is absent does not conform, while one that is present with the value null does.

Guarded recursive descriptors are unfolded while visiting the complete finite value, without the analysis projection's node cutoff. Allowed values and type constraints are separate declaration checks, not part of structural membership. Functions are checked through their retained runtime signatures and conservative type conformance, not through a structural result approximation.

The functions work on resolved types; see Feel.Type.resolve/2.

Summary

Functions

The instance of operator (Table 62): returns true when value is an instance of type.

Returns the type of a value (Table 43).

Returns true when value fits type: its type conforms to type, checked element by element so that no join is needed.

Functions

instance?(value, type)

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

The instance of operator (Table 62): returns true when value is an instance of type.

Implicit conversions do not apply, so a singleton list is not an instance of the type of its element.

iex> Feel.Type.Instance.instance?([Decimal.new(123)], {:list, :number})
true
iex> Feel.Type.Instance.instance?(Decimal.new(123), :string)
false
iex> Feel.Type.Instance.instance?(nil, :null)
true
iex> Feel.Type.Instance.instance?(nil, :number)
false
iex> Feel.Type.Instance.instance?(Decimal.new(1), {:list, :number})
false

of(value)

@spec of(Feel.Value.t()) :: Feel.Type.t()

Returns the type of a value (Table 43).

  • A list has the type list<T> where T is the join of the types of its elements (see Feel.Type.Conformance.join/2). The empty list is list<Null>, and a list of unrelated elements is list<Any>.
  • A context has one member per entry.
  • A range has the type range<T> where T is the type of its endpoints; an undefined endpoint is ignored.
  • A function has the type of its signature.
  • A term that is not a value of FEEL (a float or an atom that a host bound, D-39), alone or in a list or a context, has the type Any: Feel.eval/3 makes the scope of its bindings from their types.

Examples

iex> Feel.Type.Instance.of(Decimal.new(1))
:number
iex> Feel.Type.Instance.of(["a", "b"])
{:list, :string}
iex> Feel.Type.Instance.of(["a", true, Decimal.new(123)])
{:list, :any}
iex> Feel.Type.Instance.of(%{"name" => "Peter", "age" => Decimal.new(30)})
{:context, %{"age" => :number, "name" => :string}}
iex> Feel.Type.Instance.of([:foo])
{:list, :any}

value_conforms?(value, type)

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

Returns true when value fits type: its type conforms to type, checked element by element so that no join is needed.

null fits every type, a list fits list<T> when all its elements fit T (the empty list fits every list type), a context fits context<N> when it has every member of N with a fitting value, and a range fits range<T> when its defined endpoints fit T. A function fits a function type when its signature conforms to it.

iex> Feel.Type.Instance.value_conforms?(nil, :number)
true
iex> Feel.Type.Instance.value_conforms?(nil, {:non_null, :number})
false
iex> Feel.Type.Instance.value_conforms?(Decimal.new(1), {:non_null, :number})
true
iex> Feel.Type.Instance.value_conforms?([], {:list, :string})
true
iex> Feel.Type.Instance.value_conforms?(%{"a" => nil}, {:context, %{"a" => :number}})
true
iex> Feel.Type.Instance.value_conforms?(%{}, {:context, %{"a" => :number}})
false