Feel.Type.Conversion (feel v0.1.0)

Copy Markdown View Source

The implicit conversions of DMN 1.6, section 10.3.2.9.4.

When a value sits where a particular type is expected, it can sometimes be converted to that type without losing information. The conversions are:

  • from a singleton list: a list with exactly one element, where the element's type is expected, becomes that element.
  • to a singleton list: a value, where a list of its type is expected, becomes a one-element list.
  • date to date and time: a date becomes a date and time at midnight UTC.
  • decimal to integer is done by the built-in functions that expect integers, not here.
  • to the signature that fits (D-197): a built-in function that has a signature for each kind of its argument, where a function type is expected that none of its function types is within, becomes the function of the signature that fits. It is not in the list of the specification, whose lattice has no type for such a function; the DMN TCK passes abs to a function<number> -> number (0092 #014).

They apply where a value is bound to a parameter, to the source of a filter or iteration, and to the output of a decision. They do not apply to the operands of operators.

Guarded recursive targets from Feel.ItemDefinitions use the same conversions after unfolding their declaration head. A recursive collection, including an alias of one, can wrap a singleton; a recursive context can unwrap one. Membership checks the full finite value, without converting fields inside contexts. Allowed values and type constraints are separate declaration checks: coerce/3 and convert/3 alone do not enforce them.

The order

The specification gives no order. coerce/3 checks conformance first, so a value that already fits is returned unchanged: a singleton list passed for an Any parameter stays a list, and null is not wrapped. Only when it does not fit are the conversions tried, in this order: from a singleton list, to a singleton list, date to date and time, to the signature that fits. At most one is applied.

With compose: true, the two combinations the notes describe are also allowed: a singleton list holding a date where a date and time is expected, and a date where a list of date and time is expected.

Summary

Types

One conversion that was applied: :from_singleton (a one-element list became its element), :to_singleton (a value became a one-element list), :date_to_date_time, :function_signature (a built-in function became its signature that fits the function type, D-197), or :decimal_to_integer (a number lost its fractional part: only an argument of a built-in function that wants an integer, Feel.Builtin.Arguments, since the types have no integer, and convert/3 does not make it).

Functions

Converts value to fit type, or returns :error when it cannot be done. The caller turns :error into null and a diagnostic.

Like coerce/3, and also says what was done: the steps that were applied, in order, or [] for a value that fits as it is.

Describes steps in a sentence, for a diagnostic.

Types

step()

@type step() ::
  :from_singleton
  | :to_singleton
  | :date_to_date_time
  | :function_signature
  | :decimal_to_integer

One conversion that was applied: :from_singleton (a one-element list became its element), :to_singleton (a value became a one-element list), :date_to_date_time, :function_signature (a built-in function became its signature that fits the function type, D-197), or :decimal_to_integer (a number lost its fractional part: only an argument of a built-in function that wants an integer, Feel.Builtin.Arguments, since the types have no integer, and convert/3 does not make it).

Functions

coerce(value, type, options \\ [])

@spec coerce(Feel.Value.t(), Feel.Type.t(), [{:compose, boolean()}]) ::
  {:ok, Feel.Value.t()} | :error

Converts value to fit type, or returns :error when it cannot be done. The caller turns :error into null and a diagnostic.

Options

  • :compose - also allow the two combined conversions. Defaults to false.

Examples

iex> Feel.Type.Conversion.coerce(["foobar"], :string)
{:ok, "foobar"}
iex> Feel.Type.Conversion.coerce(Decimal.new(3), {:list, :number})
{:ok, [Decimal.new(3)]}
iex> Feel.Type.Conversion.coerce(["a", "b"], :string)
:error
iex> Feel.Type.Conversion.coerce(["foobar"], :any)
{:ok, ["foobar"]}

convert(value, type, options \\ [])

@spec convert(Feel.Value.t(), Feel.Type.t(), [{:compose, boolean()}]) ::
  {:ok, Feel.Value.t(), [step()]} | :error

Like coerce/3, and also says what was done: the steps that were applied, in order, or [] for a value that fits as it is.

iex> Feel.Type.Conversion.convert("foobar", :string)
{:ok, "foobar", []}
iex> Feel.Type.Conversion.convert(["foobar"], :string)
{:ok, "foobar", [:from_singleton]}
iex> Feel.Type.Conversion.convert(Decimal.new(3), {:list, :number})
{:ok, [Decimal.new(3)], [:to_singleton]}
iex> {:ok, %Feel.Value.DateTime{}, [:date_to_date_time]} =
...>   Feel.Type.Conversion.convert(~D[2020-01-01], :date_and_time)
iex> {:ok, [%Feel.Value.DateTime{}], steps} =
...>   Feel.Type.Conversion.convert(~D[2020-01-01], {:list, :date_and_time}, compose: true)
iex> steps
[:to_singleton, :date_to_date_time]
iex> Feel.Type.Conversion.convert(["a", "b"], :string)
:error
iex> {:ok, abs} = Feel.Builtin.value("abs")
iex> {:ok, number, [:function_signature]} = Feel.Type.Conversion.convert(abs, {:function, [:number], :number})
iex> Feel.Value.Function.types(number)
[{:function, [:number], :number}]

describe(steps)

@spec describe([step()]) :: String.t()

Describes steps in a sentence, for a diagnostic.

iex> Feel.Type.Conversion.describe([:from_singleton])
"a list with one element is replaced by that element"
iex> Feel.Type.Conversion.describe([:to_singleton, :date_to_date_time])
"a value is wrapped in a list, and then a date becomes a date and time at midnight UTC"
iex> Feel.Type.Conversion.describe([:from_singleton, :decimal_to_integer])
"a list with one element is replaced by that element, and then the fractional part of a number is discarded"
iex> Feel.Type.Conversion.describe([:function_signature])
"a built-in function with a signature for each kind it takes becomes the one that fits the type"