on master help sign in
Docs: record-update removal + unify open-row support

RECORD_MIGRATION_SPEC.md rule #2 (migrating record-update) marked removed — {base | f v} no longer parses, use base.unify base {f v}. stdlib.md unify entry was stale: it claimed both arguments need closed record shapes and open rows are a type error. After b27826f5 unify handles open rows (lambda params / relation rows) via row-splitting, so the case … -> unify r {..} ; _ -> r pattern type-checks. Rewrote that section and the example.

commit7e3390f0e4136b346ef9a2e4c2db18ff0d5ebd3b authoriko <mail@iko.soy> dateMon 3 Aug 2026, 14:07 (2 months ago) parentb27826f
2 files changed+10−6
.hermes/RECORD_MIGRATION_SPEC.md+3−2 stdlib.md+7−4
.hermes/RECORD_MIGRATION_SPEC.md+3−2
@@ -24,8 +24,9 @@ a nested record (`{...}`), a list (`[...]`), an already-parenthesized expr.
2424 `{name: e.name}` -> `{name e.name}` (field-access is atomic)
2525 `{x: {a: 1}}` -> `{x {a 1}}` (nested record, recurse)
2626
27−2. Record UPDATE `{base | f: v, g: w}` -> `{base | f v g w}`
28− - keep `|`, same value-parenthesization rule.
27+2. Record UPDATE — REMOVED. `{base | f: v, g: w}` / `{base | f v g w}` no
28+ longer parse. Use `base.unify base {f v g w}` instead (right-biased merge;
29+ it is update AND extension, and works on open rows like lambda params).
2930
30313. `with` BLOCK `with {x: e, y: e2} body` -> `with {x e y e2} body`
3132 - field names are the bindings; same value-parenthesization rule.
stdlib.md+7−4
@@ -949,10 +949,13 @@ unify {name "l" a 1} {name "r" c 3.0} -- {a: 1, c: 3.0, name: "r"}
949949unify {a 1} {a 2} -- {a: 2}
950950```
951951
952−Both arguments must have **statically-known, closed record shapes** — passing a
953−value whose record type has an open (unresolved) row is a type error. Relation
954−rows work: `map (\row -> unify row {active 1}) (full *items)` merges defaults
955−into every row.
952+Arguments may be closed records **or open rows**. Merging into an open row — a
953+lambda parameter or relation row whose full shape isn't pinned at the call
954+site — overlays the right's fields and constrains the base to contain them, so
955+`case r.id == x of Bool.True {} -> unify r {balance y}; Bool.False {} -> r`
956+type-checks (the merged result and the bare row agree on the other fields).
957+Relation rows work: `map (\row -> unify row {active 1}) (full *items)` merges
958+defaults into every row. A genuinely non-record argument is a type error.
956959
957960`unify` is shape-dependent — its result type is a function of the two argument
958961field names, computed by the type checker at the call site rather than by a