# `Feel.Analysis.NullReasons`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel/analysis/null_reasons.ex#L1)

Why an operation can be `null` for the values of its operands (D-161, step B).

A type has `Null` only if a value can cause it (D-161), and a modeler who meets one has to be told what to change. The
analysis of the sets says that an operation can be `null` (`Feel.Analysis.Intervals`, `DateArithmetic` and the rest);
this module says **which values do it**, by asking the run. For the operands' types it takes the values at which an
operation is most likely to fail (`Feel.Type.Witness.edges/2`: zero, the smallest numbers there are, the ends of the sets,
the largest number, the first and last day of the calendar), runs the operation on every pair
(`Feel.Runtime.Arithmetic.operate/3`), and keeps for each cause the first pair that the run turns down. So a reason is a
fact and not a guess: the pair is in the operands' types, and the run says `null` for it, with the code that the run gives.

| Cause | The run's code | When |
| ----- | -------------- | ---- |
| a division by zero | `:division_by_zero` | the divisor is zero; a zero to a negative power |
| too large to write | `:overflow` | the result is past the range of decimal128 |
| too small to write | `:overflow` | the result is not zero and needs a digit below 1E-6176 (D-59) |
| no value for a power | `:argument_out_of_domain` | a negative base to an exponent that is not whole |
| too many digits | `:argument_out_of_domain` | an amount of 34 digits or more, which is not a number (the months of a duration) |
| a year out of the calendar | `:date_out_of_range` | a date, or a date and time, moved past year 999999999 |
| a zone on one only | `:offset_mismatch` | the difference of a value that has a zone and one that has none |

The search is not complete: a cause whose values are not at an edge is missed. It is sound, though, and it tests the
analysis from the other side: where the sets say an operation can be `null` and no pair of the edges does it, the sets are
not exact there (`test/feel/analysis/null_reasons_test.exs` looks for that).

    iex> a = [{:number, Feel.Type.NumberSet.interval(Decimal.new(0), Decimal.new(150))}]
    iex> b = [{:number, Feel.Type.NumberSet.point(Decimal.new("0.5"))}]
    iex> [%{cause: :too_small, left: left}] = Feel.Analysis.NullReasons.find(:multiply, a, b)
    iex> Decimal.to_string(left)
    "1E-6176"

# `call_reason`

```elixir
@type call_reason() :: %{
  cause: atom(),
  arguments: [Feel.Value.t()],
  message: String.t() | nil
}
```

A call of a built-in function that is `null` for these values of its arguments, and the error the run gives, if any.

# `cause`

```elixir
@type cause() ::
  :division_by_zero
  | :too_large
  | :too_small
  | :out_of_domain
  | :too_many_digits
  | :date_out_of_range
  | :offset_mismatch
```

What makes the run turn the operation down.

# `label`

```elixir
@type label() :: String.t() | nil | :constant
```

What an operand is called in a sentence: its name, `nil` for an expression, `:constant` for a value that is written.

# `reason`

```elixir
@type reason() :: %{cause: cause(), left: Feel.Value.t(), right: Feel.Value.t()}
```

A pair of values of the operands for which the run gives `null`, and why.

# `call_sentence`

```elixir
@spec call_sentence(String.t(), call_reason()) :: String.t()
```

The sentence that tells one reason a call is `null`: the call with the values, and what the run says.

    iex> reason = %{cause: :argument_out_of_domain, arguments: ["", Decimal.new(0)], message: "start position is 0"}
    iex> Feel.Analysis.NullReasons.call_sentence("substring", reason)
    ~s|substring("", 0) is null: start position is 0|

# `find`

```elixir
@spec find(Feel.IR.arithmetic_operator(), Feel.Type.Union.t(), Feel.Type.Union.t(),
  same: boolean(),
  grains: {integer() | nil, integer() | nil}
) :: [reason()]
```

The reasons an arithmetic operation can be `null`, for the types of its operands: for each cause that some pair of edge
values shows, the first pair. The operands' `null`, if they have it, is left out: it is the operand that is `null`, and not
the operation.

With `same: true` the two operands are one name (`d / d`), so they have one value and only a pair of equal values is a reason.

    iex> d = [{:number, Feel.Type.NumberSet.interval(Decimal.new(0), Decimal.new(150))}]
    iex> Enum.map(Feel.Analysis.NullReasons.find(:divide, d, d), & &1.cause)
    [:division_by_zero, :too_large, :too_small]
    iex> Enum.map(Feel.Analysis.NullReasons.find(:divide, d, d, same: true), & &1.cause)
    [:division_by_zero]

# `find_call`

```elixir
@spec find_call(String.t(), [Feel.Type.Union.t()], [{:max_calls, pos_integer()}]) :: [
  call_reason()
]
```

The reasons a call of the built-in function `name` can be `null`, for the types of its arguments: for each cause (the code
of the error the run gives, or `:null` for a `null` with no error), the first values of the arguments that the run turns down.
The values are those at the edges of the types (`Feel.Type.Witness.edges/2`), at most `:max_calls` of them (256), and the
function is the run's own (`Feel.Builtin.call/4`). The arguments' `null`, if they have it, is left out.

    iex> a = [{:number, Feel.Type.NumberSet.interval(Decimal.new(0), Decimal.new(150))}]
    iex> [reason] = Feel.Analysis.NullReasons.find_call("log", [a])
    iex> {reason.cause, Enum.map(reason.arguments, &Decimal.to_string/1)}
    {:argument_out_of_domain, ["0"]}

# `sentence`

```elixir
@spec sentence(reason(), {label(), label()}) :: String.t()
```

The sentence that tells one reason, with the names of the operands (`nil` for an operand that is not a name, `:constant`
for one that is a value written in the expression, which is not told).

    iex> reason = %{cause: :division_by_zero, left: Decimal.new(1), right: Decimal.new(0)}
    iex> Feel.Analysis.NullReasons.sentence(reason, {:constant, "x"})
    "a division by zero when x = 0"
    iex> reason = %{cause: :too_small, left: Decimal.new(1, 1, -6176), right: Decimal.new("0.5")}
    iex> Feel.Analysis.NullReasons.sentence(reason, {"x", :constant})
    "a result too small to write when x = 1E-6176"

# `show`

```elixir
@spec show(Feel.Value.t()) :: String.t()
```

A value as it is written in a reason: a number of a size that is not usual in the scientific notation (`1E-6176`, and not
the six thousand digits of it), a duration of such an amount in its seconds or months.

    iex> Feel.Analysis.NullReasons.show(Decimal.new(1, 1, -6176))
    "1E-6176"
    iex> Feel.Analysis.NullReasons.show(Decimal.new(150))
    "150"
    iex> Feel.Analysis.NullReasons.show(Feel.Value.DaysAndTimeDuration.new(Decimal.new(1, 1, -6176)))
    "PT1E-6176S"
    iex> Feel.Analysis.NullReasons.show(["a", nil, %{"k" => true}])
    ~s|["a", null, {k: true}]|

---

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