# `Feel.Type.Conversion`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/type/conversion.ex#L1)

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.

# `step`

```elixir
@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).

# `coerce`

```elixir
@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`

```elixir
@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`

```elixir
@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"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
