What the code looks like now
The conformability contract — a statically known mismatch is a compile
error, dims that cannot be compared statically get a statement-level
runtime guard, provably equal dims need nothing, never warn-and-proceed —
is spelled three times, in three files, by three functions that have to be
kept in agreement by hand:
guard_conformable_dims() in R/r2f-matrix-blas.R, added by PR 7 for the
BLAS/LAPACK lowerings. It is the only one that renders a statically known
extent as the literal rather than size(x).
emit_elementwise_size_guard() in R/r2f-operators-helpers.R, used by
maybe_reshape_vector_matrix() for the elementwise operators. Its
caller does the check-then-emit dance inline at five separate sites.
- An inline per-axis guard inside
check_ifelse_branch_shape()
(R/r2f-conditionals.R), whose private ifelse_axis_verdict()
re-implements check_elementwise_lengths() — minus the zero-length
policy, which is why an ifelse() branch of statically known length 0
slips through where an elementwise operand of the same length is
rejected.
Two smaller duplications sit alongside:
check_conformable() (R/r2f-matrix-blas.R) is dims_match()
(R/r2f-operators-helpers.R) written in list form — same two branches,
same answers, wrapped in list(ok=, unknown=) so that both call sites
(solve() routing, the cbind/rbind common-dim check) then have to
destructure a verdict whose unknown field they immediately collapse
back to a boolean.
- The stay-in-the-real-domain floor spelling —
aint(x) plus a merge()
correction, written the way it is because Fortran's FLOOR() returns an
integer and would overflow on a large double — appears verbatim in the
floor() handler (R/r2f-math.R) and in double %/%
(R/r2f-arithmetic.R).
Why it matters
- A policy fix has to land three times. Each of the earlier PRs in this
series tightened the conformability rules, and each had to be applied
once per emitter. A missed emitter fails silently — it keeps compiling,
just with the old semantics. The ifelse() zero-length hole above is
exactly that failure, already in the tree.
- The three emitters have already drifted. They disagree on whether a
known extent is spelled as a literal or as size(x), on whether a
zero-length operand is refused, and on whether a NULL hoist is a clean
error or a crash. None of these differences is intentional.
check_conformable() invites the wrong reading. Its
unknown = TRUE result looks like it defers to a runtime guard, but
neither call site can emit one — a cbind/rbind output dim has to be
known to declare the variable at all. Both sites turn unknown into the
same compile error, which is what dims_match() already means.
Expected behavior
No behavior change — this is a maintainability issue. The conformability
policy should be spelled once, in one function, that every enforcement
point calls: the elementwise operators, ifelse(), and the BLAS/LAPACK
lowerings. check_conformable() should be its dims_match() self at both
call sites, and the real-domain floor spelling should live in one place.
The combinatorial conformability grid added earlier in this series
(QUICKR_FULL_GRID=1) is what makes such a change safe to attempt: it
pins values, typeof(), shapes, and the documented compile/runtime errors
for every (mode, shape, operator) cell against plain R.
What the code looks like now
The conformability contract — a statically known mismatch is a compile
error, dims that cannot be compared statically get a statement-level
runtime guard, provably equal dims need nothing, never warn-and-proceed —
is spelled three times, in three files, by three functions that have to be
kept in agreement by hand:
guard_conformable_dims()in R/r2f-matrix-blas.R, added by PR 7 for theBLAS/LAPACK lowerings. It is the only one that renders a statically known
extent as the literal rather than
size(x).emit_elementwise_size_guard()in R/r2f-operators-helpers.R, used bymaybe_reshape_vector_matrix()for the elementwise operators. Itscaller does the check-then-emit dance inline at five separate sites.
check_ifelse_branch_shape()(R/r2f-conditionals.R), whose private
ifelse_axis_verdict()re-implements
check_elementwise_lengths()— minus the zero-lengthpolicy, which is why an
ifelse()branch of statically known length 0slips through where an elementwise operand of the same length is
rejected.
Two smaller duplications sit alongside:
check_conformable()(R/r2f-matrix-blas.R) isdims_match()(R/r2f-operators-helpers.R) written in list form — same two branches,
same answers, wrapped in
list(ok=, unknown=)so that both call sites(
solve()routing, the cbind/rbind common-dim check) then have todestructure a verdict whose
unknownfield they immediately collapseback to a boolean.
aint(x)plus amerge()correction, written the way it is because Fortran's
FLOOR()returns aninteger and would overflow on a large double — appears verbatim in the
floor()handler (R/r2f-math.R) and in double%/%(R/r2f-arithmetic.R).
Why it matters
series tightened the conformability rules, and each had to be applied
once per emitter. A missed emitter fails silently — it keeps compiling,
just with the old semantics. The
ifelse()zero-length hole above isexactly that failure, already in the tree.
known extent is spelled as a literal or as
size(x), on whether azero-length operand is refused, and on whether a
NULLhoist is a cleanerror or a crash. None of these differences is intentional.
check_conformable()invites the wrong reading. Itsunknown = TRUEresult looks like it defers to a runtime guard, butneither call site can emit one — a cbind/rbind output dim has to be
known to declare the variable at all. Both sites turn
unknowninto thesame compile error, which is what
dims_match()already means.Expected behavior
No behavior change — this is a maintainability issue. The conformability
policy should be spelled once, in one function, that every enforcement
point calls: the elementwise operators,
ifelse(), and the BLAS/LAPACKlowerings.
check_conformable()should be itsdims_match()self at bothcall sites, and the real-domain floor spelling should live in one place.
The combinatorial conformability grid added earlier in this series
(
QUICKR_FULL_GRID=1) is what makes such a change safe to attempt: itpins values,
typeof(), shapes, and the documented compile/runtime errorsfor every (mode, shape, operator) cell against plain R.