Skip to content

Prototype evaluation — the thirteen D probes

The examples/ tree holds thirteen runnable, CI-verified D programs. Each isolates one design decision for a future Sparkles units library and proves it end to end — the intended compile-time failures are turned into passing static assert(!__traits(compiles, …)) checks, so every program compiles, runs, and is exercised by dub run :ci -- --example-files on each pass. This page evaluates them side by side across a fixed set of axes and ends with what a Sparkles library should take from each, mapped to the comparison capstone's open decisions.

Last reviewed: July 5, 2026

The shared spine

Twelve of the thirteen share one skeleton, so the comparison is really about where each departs from it:

  • a dimension is a compile-time value — a struct Dim of integer (or Rational) exponents passed as a template value parameter, its own unique normal form;
  • combine(a, b, sign) is the free-abelian-group operation (multiply / divide);
  • arithmetic is opBinary + mixin, with +/- confined to one grade and *// total;
  • a CTFE unitString renders the label;
  • cross-dimension misuse is rejected by static assert(!__traits(compiles, …)).

Every core is @safe pure nothrow @nogc. The single structural outlier is quantity-runtime-expected.d, which moves the dimension into a runtime field. Read quantity-zn-graded.d first; the others are diffs against it.

Evaluation axes

  • Representation — how a dimension is encoded, its exponent domain, and its basis.
  • Checking & cost — when a mismatch is caught, and what a quantity costs at run time.
  • Extensibility — how a new dimension or unit is introduced (the most library-relevant axis, and the one that cuts the field most cleanly).
  • Integration — how the approach composes with sparkles:math's Vector.
  • Capability fingerprint — the expressiveness features (fractional powers, affine, kind, logarithmic, engineered diagnostics, generic polymorphism). This grid is sparse by design: each prototype deliberately owns ~one capability, and the empty cells are the point — they map the design space by division of labour, not by each probe doing everything.

Near-constant properties are left to the prose: dependencies track the Vector column (anything touching Vector pulls sparkles:math); attribute discipline is uniform (@safe pure nothrow @nogc); size and the shared D features (CTFE, template value parameters, opBinary, mixin) carry no signal — only the distinctive feature per file does.

At-a-glance

PrototypeModels (system · theory)Dimension encodingExponent · basisCheckedRuntime costA new dimension is added byVector composition
quantity-zn-gradedfree abelian groupDim value parameterℤ³ · closedcompileerased (bare double)editing the central Dimscalar (n/a)
quantity-rational-exponentsmechanisms (fractional)Dim{Rational …} value parameterℚ³ gcd · closedcompileerasedediting the central Dimscalar (n/a)
quantity-erasuremechanisms (erasure theorem)Dim value parameterℤ³ · closedcompileerased — layout-proven¹editing the central Dimscalar (n/a)
quantity-affine-torsortorsor · SwiftQuantity!(dim, Payload)ℤ³ · closedcompile²Vec3 payloadediting the central Dimordering A (real Vector)
quantity-kind-tagsuom · §kindsQuantity!(dim, Kind, Payload)ℤ⁴ · closedcompileerasedcentral Dim + a Kind enumA-ready (Payload param)
quantity-nominalsquants · Swiftone struct per quantitynone (nominal)compileerased / Vec3a new struct + wiring every productbespoke struct per quantity³
quantity-runtime-expectedPint · UCUM/QUDTruntime Dim fieldℤ⁴ runtime int**runtime — Expected**⁴carries a Dim (4 ints)a runtime table rowordering A (RVec3)
quantity-unit-in-typemp-units · Au · uomQuantity!(Unit{dim + rational scale})ℤ³ + scalecompileerased (scale in the type)editing the central Dimordering A
quantity-diagnosticsAu · mp-units · §6Quantity!(dim, kind, unit)ℤ⁴ · closedcompile — engineerederasedediting the central Dimordering A
quantity-polymorphismmechanisms · KennedyQuantity!(dim, Payload)ℤ³ · closedcompileVec3editing the central DimA + generic dot/cross
quantity-open-basisAu · mp-units (basis)CTFE Gen[] of (name, exp) · opencompileerasedminting a name (base("sr"))ordering A
quantity-logarithmicPint · Unitfulstruct Stops / Decibelsnone (log)compileerasedn/a (not a grade)ordering B noted (nonlinear)
quantity-vector-compositionmechanisms · torsorQuantity!(dim, Payload) + local Vec(T,N)ℤ³ · closedcompileVec3editing the central DimA vs B — proves the isNumeric!T blocker

¹ static foreach asserts sizeof/alignof/offsetof/array-layout equal a bare double for four grades — the checkable half of erasure (codegen identity is left to the optimizer, stated honestly). ² Affine misuse is rejected by a mix of !__traits(compiles, …) and an engineered static assert(0, "… not an affine operation"). ³ The composition anti-pattern: with no dim to parameterize on, Position and Displacement each wrap the same Vec3 in their own bespoke struct — nominal typing multiplies the surface that composes with Vector instead of factoring it. ⁴ add/sub return Expected!(RQuantity, DimError, NoGcHook) — an err, not a throw; the one deliberate throwing path uses recycledErrorInstance to stay @nogc. ⁵ checkAddable!(A, B) fires static assert(false, "cannot add Radiance [W·m⁻²·sr⁻¹] to Irradiance [W·m⁻²] — dimensions differ"), plus a pragma(msg, …) printing the engineered sentence next to the raw mangled type.

Capability fingerprint

Which expressiveness axis each prototype owns ( = demonstrated here; blank = out of scope for that probe, covered by another):

PrototypeFractionalAffineKindLogarithmicEngineered diagnosticsGeneric polymorphism
quantity-rational-exponents
quantity-affine-torsor
quantity-kind-tags✓ tag¹
quantity-nominal✓ nominal²
quantity-diagnostics
quantity-polymorphism
quantity-logarithmic

¹ A flat comparability tag: HzBq, plane-angle ≠ ratio — but erased under ×/÷ (AngularVelocity × Time reverts to the default kind), so it is a tag, not an algebra. ² Nominal typing gives TorqueEnergy (and RadianceIrradiance) for free, but forfeits the exponent group entirely and cannot name an undeclared product — Position × Position has no type. A genuine fork against the exponent-vector-plus-tag model, not a stacking option. The seven prototypes absent from this grid own a representation axis instead (erasure → runtime cost, runtime-expected → checking epoch, unit-in-type → unit storage, open-basis → basis, vector-composition → linear-algebra composition, and the two core ℤ/ℚ probes → exponent domain).

Findings by axis

Exponent domain is settled; the basis is the real fork. ℤ and ℚ are one CTFE parameter apart (ex-z vs ex-q's gcd-normalized Rational, which makes sqrt total while m^(1/2) + m stays rejected), matching the survey's finding that practice drifted to ℚ. The sharper split is closed vs open basis: quantity-open-basis mints a new base dimension by naming it (base("sr"), base("sample")) with no core edit — at the cost of per-instantiation CTFE normalization and the loss of the fixed-width int[3] layout guarantee that quantity-erasure leans on.

Checking is compile-time almost everywhere; the one runtime probe pays for it in bytes. Twelve prototypes reject m + s at compile time; quantity-runtime-expected alone carries a Dim field at run time and returns an Expected err rather than a compile error or a throw — the honest cost of the Pint/UCUM registry pole is four ints per value where the others store a bare double. quantity-erasure is the mirror image: it machine-checks that the grade is fully erased from the object layout.

Extensibility is the most decision-relevant axis, and it cuts four ways. Adding a dimension means: editing the central Dim (the ℤ³ family — simple, closed); minting a name (open-basis — extensible, unnormalized); writing a new struct and hand-wiring every product (nominal — the surface explodes); or inserting a runtime table row (runtime-expected — data-driven, unchecked). This axis, not the exponent domain, is what a library actually lives with.

The kind question is a fork, not a ladder. quantity-kind-tags shows the flat-tag rung (distinguishes same-dimension quantities, but the tag is erased under multiplication); quantity-nominal shows the opposite pole (kind for free via distinct types, but no exponent algebra and no nameable products). They are mutually exclusive designs, and quantity-diagnostics demonstrates the third lever — making the mismatch message a product: a checkAddable template emits domain-language prose instead of a mangled typenum-style leak.

Four prototypes independently rediscover the same three Vector gaps.affine-torsor, nominal, polymorphism, and vector-composition all compose a Quantity with sparkles:math's Vector, and each hits the same wall: Vector(T, N) is constrained if (isNumeric!T) (so Vector!(Quantity, N) is unnameable), its dot is dimension-blind cast(CommonType) (a length·length dot comes back a scalar, not an area), and it ships no 3-D cross/magnitude/normalize. quantity-vector-composition makes this a machine-checked fact and shows a ~30-line element-generic Vec compiling ordering B where Vector refuses. The convergence of four independent probes on the same finding is itself the signal — this is the co-design pressure detailed in the capstone's composition subsection.

Forward inference is enough; a solver is not. Every compile-time prototype infers result grades through ×/÷ for free; quantity-polymorphism extends this to generic dot/cross/magnitude/normalize and then names the ceiling — D dispatches on argument types, never on the result type, so there is no inverse of sqr and no Kennedy-style AG-unification. D is an evaluator, not a solver; the survey shows that rung is a two-system niche and a maintenance liability, so the ceiling is not a loss.

What a Sparkles units library should take

Each probe contributes one decision, mapped to the comparison capstone's Part IV open decisions:

FromTakeOpen decision it settles / frames
ex-z + ex-qa CTFE value-Dim exponent vector; ship an ℤ surface with a ℚ escape hatchExponent domain
quantity-erasurekeep the sizeof/offsetof layout asserts as a CI guard on the zero-cost claimErasure guarantee
quantity-affine-torsora distinct affine Point/Vec (torsor) split, N-generic and unit-awareAffine quantities
quantity-kind-tags · nominalchoose the kind model as a fork: a flat erased tag or nominal per-quantity types — not bothKind system
quantity-unit-in-typedecide unit-in-type + lazy conversion vs normalize-to-base (this probe shows the former works)Unit storage
quantity-diagnosticsa checkAddable gate that emits domain-language static assert proseDiagnostics strategy
quantity-open-basisdecide open generator set vs closed vector (this probe shows mint-by-name works)Registry vs closed / basis
quantity-runtime-expectedif a runtime companion is wanted, make it Expected-returning, @nogc, never a throwRuntime companion
quantity-logarithmictreat logarithmic units as their own algebra (defer to a later version)Angle & logarithmic policy
quantity-polymorphismrely on forward IFTI; do not chase a solver — D cannot, and the survey says do notSolver (evaluators vs solvers)
quantity-vector-compositionrelax Vector's isNumeric!T → an isScalar concept; make dot/cross/magnitude element-driven; add the missing 3-D opsComposition with sparkles:math

Composite recommendation. The prototypes point at a coherent design: a compile-time CTFE value-Dim core (ℤ surface, ℚ escape) with erased representation guarded by layout asserts; a distinct affine Point/Vec torsor layer; engineered checkAddable diagnostics as a first-class feature; an optional Expected-based runtime companion for data-driven units; and a co-designed sparkles:math whose Vector accepts Quantity elements (isScalar concept, element-driven dot/cross, N-generic unit-aware affine types). The two genuine forks left for the docs/specs/ proposal to decide are the kind model (flat tag vs nominal) and the basis (closed vector vs open generators).

Sources

The thirteen prototypes under examples/ (each linked individually above) — each CI-verified by dub run :ci -- --example-files and cross-linked to the system and theory pages it models — together with the comparison capstone (Part IV open decisions and the "Composition with sparkles:math" subsection) this evaluation feeds. No claim here is not already grounded in a runnable prototype.