internal design

Design goal

immut and mutable check the same preconditions (squareness, bounds, compatible shapes) and must report them identically. internal keeps one implementation of each check, so the two packages cannot drift apart, without exposing the helpers to downstream code.

Mathematical background

Each guard is the characteristic test of a domain from the error design: dom⁡(tr⁡)={A:r=c}\operatorname{dom}(\operatorname{tr}) = \{A : r = c\}, dom⁡(⋅)={(A,B):cA=rB}\operatorname{dom}(\cdot) = \{(A, B) : c_A = r_B\}, and so on. Writing the test once as a total function χ:X→Unit+E\chi : X \to \mathrm{Unit} + E gives both forms of a partial operation:

checked(x)=χ(x)> ⁣ ⁣> ⁣ ⁣=(_↦Ok(f(x))),unchecked(x)={f(x)χ(x)=Ok(())abortotherwise.\mathtt{checked}(x) = \chi(x) \mathbin{>\!\!>\!\!=} (\_ \mapsto \mathrm{Ok}(f(x))), \qquad \mathtt{unchecked}(x) = \begin{cases} f(x) & \chi(x) = \mathrm{Ok}(()) \\ \text{abort} & \text{otherwise.} \end{cases}

Deriving the aborting guard from the checked one (ensure_x matches on ensure_x_checked) makes the two agree on every input by construction.

Design decisions

An internal package

MoonBit’s internal-package rule restricts imports of Luna-Flow/linear-algebra/internal to packages of this module. The helpers can therefore change freely, while their effects are specified on the public methods.

HasShape separate from MatrixShape

@algebra.MatrixShape has the same method, but algebra is experimental and the concrete packages should not depend on it. HasShape gives the guards a bound that immut and mutable implement locally. The repetition is the cost of keeping the stable concrete packages independent of the experimental layer.

Row before column

ensure_index_in_bounds checks the row first, then the column, and each against its own dimension. Checking the flat offset rc′+cr c' + c against rc′rc' instead would accept a column index that spills into the next row.

Correctness and invariants

  • ensure_x(m) aborts if and only if ensure_x_checked(m) returns Err.
  • Each checked guard returns the error kind listed on the API page, with a fixed message.
  • All guards run in O(1)O(1) and only call shape.

Alternatives rejected

  • Duplicating the checks in each package, which is how bounds behaviour diverged before the 0.4 line.
  • Public helpers. They would become API that downstream code depends on.

Boundaries

internal checks shapes and indices only. It contains no arithmetic, no storage, and no checks that depend on values (singularity, emptiness of data, convergence).