Problem
A constructor value compiles to a table with a hash part: {["$ctor"] = "Data.Maybe∷Maybe.Just", value0 = x} — two string keys hashed on every allocation and every field/tag read. Measured (2e6 allocate+match iterations, PUC 5.1): the array-part representation {tag, x} is 1.4x faster and smaller (array part, no hash slots). An anti-result from the same measurements worth recording: switching the tag from an interned string to an integer buys nothing (0.92x — interned strings compare by pointer); the win comes from positional layout, not the tag type.
Approach
{TAG_STRING, v₀, v₁}: tag at [1], fields at [2..]. The IR does not change at all — Ctor/ReflectCtor/DataArgumentByIndex are already representation-abstract; this is pure lowering in Lua.hs (ReflectCtor → x[1], DataArgumentByIndex i → x[i+2]).
Prerequisites / Relations
Independent in the dependency graph — no native blocked-by edge — but scheduled to land late. The representation is FFI-visible: any fork foreign code reading .value0 / ["$ctor"] or hand-constructing values of PureScript types breaks silently. Most fork API is representation-abstract (constructors are passed in as parameters, _codePointAt(just)(nothing)-style), but a full fork audit is mandatory, plus a major package-set bump, plus a full structural-golden rewrite. Paths where the tag string escapes into output (Show-adjacent, generic deriving) need explicit review. Schedule strictly after #178–#181 collect the main wins, so the fork audit runs once.
Verification / Measurement
~1.4x faster and smaller on the allocate+match microbenchmark (2e6 iterations, PUC 5.1) that is already in #172's set. Eval goldens must not move — the representation flip is semantics-preserving, so the runtime oracle is the safety net that proves no fork or output path silently depended on the old layout.
Checklist
- Grep all package-set forks for
value0/$ctor in .lua sources; fix or confirm abstract.
- Lowering change in
Lua.hs + golden regeneration.
- Eval goldens must not move (semantic oracle).
- Major package-set bump, coordinated release.
Problem
A constructor value compiles to a table with a hash part:
{["$ctor"] = "Data.Maybe∷Maybe.Just", value0 = x}— two string keys hashed on every allocation and every field/tag read. Measured (2e6 allocate+match iterations, PUC 5.1): the array-part representation{tag, x}is 1.4x faster and smaller (array part, no hash slots). An anti-result from the same measurements worth recording: switching the tag from an interned string to an integer buys nothing (0.92x — interned strings compare by pointer); the win comes from positional layout, not the tag type.Approach
{TAG_STRING, v₀, v₁}: tag at[1], fields at[2..]. The IR does not change at all —Ctor/ReflectCtor/DataArgumentByIndexare already representation-abstract; this is pure lowering inLua.hs(ReflectCtor→x[1],DataArgumentByIndex i→x[i+2]).Prerequisites / Relations
Independent in the dependency graph — no native blocked-by edge — but scheduled to land late. The representation is FFI-visible: any fork foreign code reading
.value0/["$ctor"]or hand-constructing values of PureScript types breaks silently. Most fork API is representation-abstract (constructors are passed in as parameters,_codePointAt(just)(nothing)-style), but a full fork audit is mandatory, plus a major package-set bump, plus a full structural-golden rewrite. Paths where the tag string escapes into output (Show-adjacent, generic deriving) need explicit review. Schedule strictly after #178–#181 collect the main wins, so the fork audit runs once.Verification / Measurement
~1.4x faster and smaller on the allocate+match microbenchmark (2e6 iterations, PUC 5.1) that is already in #172's set. Eval goldens must not move — the representation flip is semantics-preserving, so the runtime oracle is the safety net that proves no fork or output path silently depended on the old layout.
Checklist
value0/$ctorin.luasources; fix or confirm abstract.Lua.hs+ golden regeneration.