Origin
Synced from Notion: RECORD / WITH — closure-based state (research note)
Content
Status
Research note, not a competitive-use proposal. Documented for completeness as an alternative approach to multi-variable REDUCE state. Unlikely to be used in 30-minute cases — too much upfront structure for the time pressure — but the technique is interesting and may inform future Loop primitives.
Problem space
Same as the existing Loop primitives (SetVar / GetVar / NewState): how do you carry multiple state variables through a single-cell REDUCE accumulator? The SetVar/GetVar approach packs state into a delimited string. RECORD/WITH takes a fundamentally different approach — it represents state as a LAMBDA closure.
Source
Discovered in charter_v0.1.4_release.xlsx, Sheet1!A8 — a 4-round Warrior-vs-Dragon combat simulation built as a single-cell REDUCE.
How it works
A WITH(...) call returns a LAMBDA. Field access is a function call:
WITH("hp", 100, "atk", 25) returns a callable
_hero("hp") calls that LAMBDA with "hp" and gets back 100
The LAMBDA's body is effectively a switch on the first argument. Field names are baked in as string literals at definition time.
RECORD("fighter", WITH(...)) wraps a record with a type tag and adds an "update" sentinel:
_hero("update", WITH("hp", 50)) → returns a new record with merged fields
- This is how field updates work in a system whose only operation is "call with a string"
Records nest natively: the REDUCE accumulator in the example is itself WITH("hero", _hero, "boss", _boss, "log", "") — a record holding two records and a string.
Comparison to SetVar / GetVar
| Dimension |
SetVar / GetVar |
RECORD / WITH |
| Representation |
Delimited string |
LAMBDA closure |
| Read |
GetVar(s, "hp") → text |
_hero("hp") → native value |
| Write |
SetVar(s, "hp", 50) |
_hero("update", WITH("hp", 50)) |
| Numeric coercion |
Required (--, +0) |
Native |
| Nested records |
Manual serialization |
First-class |
| Methods on records |
None |
Yes (e.g. _alive, _damage) |
| Per-step cost |
O(n) string parse |
O(1) closure call |
| Total loop cost |
O(n²) |
O(n) |
| Cell-size ceiling |
~32K chars of state |
None — closures aren't serialized |
| Introspection |
SplitState renders to a row |
None — closures are opaque |
| Typo safety |
Any string is a valid key, missing key returns NA |
Silent fallback on unknown strings |
Magic-string trade-off
RECORD/WITH leans on convention. Field names ("hp", "atk"), method names ("update"), and type tags ("fighter") are all string literals the implementation recognises. Typos won't error — they'll silently return the fallback branch. SetVar/GetVar avoids this because the operation is encoded in which LAMBDA you call, not in what string you pass.
Why not for competitive use
- Requires defining the record schema (constructor LAMBDA + every method) before the loop runs. In a 5-minute level that's overhead the SetVar/GetVar generic store doesn't impose.
- Magic-string typos fail silently — debugging eats clock time.
- Benefit is mostly performance and nesting depth, neither of which matter for typical case-sized state.
Where it might inform future work
- The "callable record" pattern could justify a
RECORD primitive in the library if a case ever benefits from native nesting or O(1) reads.
- The "update sentinel" trick is a neat workaround for the no-mutation constraint and worth remembering.
- A debug/introspection helper for closure-records (the missing
SplitState equivalent) would be needed before this is usable under time pressure.
Reference formula
The Warrior-vs-Dragon simulation from Sheet1!A8 of charter_v0.1.4_release.xlsx:
=LET(
_fighter, LAMBDA(_n,_hp,_atk,_def,
RECORD("fighter", WITH("name", _n, "hp", _hp, "atk", _atk, "def", _def))),
_damage, LAMBDA(_a,_d, MAX(0, _a("atk") - _d("def"))),
_hit, LAMBDA(_a,_d,
_d("update", WITH("hp", MAX(0, _d("hp") - _damage(_a, _d))))),
_alive, LAMBDA(_f, _f("hp") > 0),
_hero, _fighter("Warrior", 100, 25, 10),
_boss, _fighter("Dragon", 200, 35, 15),
_final, REDUCE(WITH("hero", _hero, "boss", _boss, "log", ""),
SEQUENCE(20), LAMBDA(_s,_rnd,
IF(OR(NOT(_alive(_s("hero"))), NOT(_alive(_s("boss")))), _s,
LET(
_b, _hit(_s("hero"), _s("boss")),
_h, IF(_alive(_b), _hit(_b, _s("hero")), _s("hero")),
WITH("hero", _h, "boss", _b, "log", _s("log") &
"R" & _rnd & ": " &
_damage(_s("hero"), _s("boss")) & " dmg to " & _b("name") &
" (" & _b("hp") & " HP)" &
IF(_alive(_b),
" | " & _damage(_b, _s("hero")) & " dmg to " & _h("name") &
" (" & _h("hp") & " HP)",
" — KO!") & CHAR(10)))))),
VSTACK(
TEXTSPLIT(_final("log"), , CHAR(10)),
"",
IF(_alive(_final("hero")),
_final("hero")("name") & " wins with " & _final("hero")("hp") & " HP!",
_final("boss")("name") & " wins with " & _final("boss")("hp") & " HP!")))
Origin
Synced from Notion: RECORD / WITH — closure-based state (research note)
Content
Status
Research note, not a competitive-use proposal. Documented for completeness as an alternative approach to multi-variable REDUCE state. Unlikely to be used in 30-minute cases — too much upfront structure for the time pressure — but the technique is interesting and may inform future Loop primitives.
Problem space
Same as the existing Loop primitives (SetVar / GetVar / NewState): how do you carry multiple state variables through a single-cell REDUCE accumulator? The SetVar/GetVar approach packs state into a delimited string. RECORD/WITH takes a fundamentally different approach — it represents state as a LAMBDA closure.
Source
Discovered in
charter_v0.1.4_release.xlsx, Sheet1!A8 — a 4-round Warrior-vs-Dragon combat simulation built as a single-cell REDUCE.How it works
A
WITH(...)call returns a LAMBDA. Field access is a function call:WITH("hp", 100, "atk", 25)returns a callable_hero("hp")calls that LAMBDA with"hp"and gets back100The LAMBDA's body is effectively a switch on the first argument. Field names are baked in as string literals at definition time.
RECORD("fighter", WITH(...))wraps a record with a type tag and adds an "update" sentinel:_hero("update", WITH("hp", 50))→ returns a new record with merged fieldsRecords nest natively: the REDUCE accumulator in the example is itself
WITH("hero", _hero, "boss", _boss, "log", "")— a record holding two records and a string.Comparison to SetVar / GetVar
GetVar(s, "hp")→ text_hero("hp")→ native valueSetVar(s, "hp", 50)_hero("update", WITH("hp", 50))--,+0)_alive,_damage)SplitStaterenders to a rowMagic-string trade-off
RECORD/WITH leans on convention. Field names (
"hp","atk"), method names ("update"), and type tags ("fighter") are all string literals the implementation recognises. Typos won't error — they'll silently return the fallback branch. SetVar/GetVar avoids this because the operation is encoded in which LAMBDA you call, not in what string you pass.Why not for competitive use
Where it might inform future work
RECORDprimitive in the library if a case ever benefits from native nesting or O(1) reads.SplitStateequivalent) would be needed before this is usable under time pressure.Reference formula
The Warrior-vs-Dragon simulation from Sheet1!A8 of
charter_v0.1.4_release.xlsx: